Skip to content

Commit 3153c36

Browse files
committed
aog wip
1 parent 009f192 commit 3153c36

1 file changed

Lines changed: 86 additions & 72 deletions

File tree

src/data_visualization/aog_in_clojure_part1.clj

Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
;; Malli - Schema validation
167167
[malli.core :as m]
168168
[malli.error :as me]
169+
[malli.util :as mu]
169170

170171
;; RDatasets - Example datasets
171172
[scicloj.metamorph.ml.rdatasets :as rdatasets]))
@@ -556,6 +557,15 @@
556557
;; **You can skim this section** - it's reference material. The schemas will be
557558
;; used by validation helpers later, and referenced in examples as needed.
558559

560+
;; ### ⚙️ Malli Registry Setup
561+
;;
562+
;; Create a registry that includes both default schemas and malli.util schemas.
563+
;; This enables declarative schema utilities like :merge, :union, :select-keys.
564+
565+
(def registry
566+
"Malli registry with default schemas and util schemas (for :merge, etc.)"
567+
(merge (m/default-schemas) (mu/schemas)))
568+
559569
;; ### ⚙️ Core Type Schemas
560570

561571
(def DataType
@@ -662,25 +672,12 @@
662672

663673
;; ### ⚙️ Layer Schema
664674

665-
(def Layer
666-
"Schema for a complete layer specification.
667-
668-
A layer is a flat map with distinctive :=... keys containing all the
669-
information needed to render a visualization layer:
670-
- Data source
671-
- Aesthetic mappings (x, y, color, size, etc.)
672-
- Plot type
673-
- Visual attributes
674-
- Optional statistical transformation
675-
- Optional faceting"
675+
(def BaseLayer
676+
"Base layer fields shared across all plot types."
676677
[:map
677678
;; Data (required for most layers)
678679
[:=data {:optional true} Dataset]
679680

680-
;; Positional aesthetics
681-
[:=x {:optional true} PositionalAesthetic]
682-
[:=y {:optional true} PositionalAesthetic]
683-
684681
;; Other aesthetics
685682
[:=color {:optional true} ColorAesthetic]
686683
[:=size {:optional true} SizeAesthetic]
@@ -692,8 +689,7 @@
692689
;; Attributes (constant visual properties)
693690
[:=alpha {:optional true} AlphaAttribute]
694691

695-
;; Plot type and transformation
696-
[:=plottype {:optional true} PlotType]
692+
;; Transformation
697693
[:=transformation {:optional true} Transformation]
698694

699695
;; Histogram-specific
@@ -704,6 +700,73 @@
704700
[:=scale-y {:optional true} ScaleSpec]
705701
[:=scale-color {:optional true} ScaleSpec]])
706702

703+
(def Layer
704+
"Schema for a complete layer specification with plottype-specific requirements.
705+
706+
Uses :multi to dispatch on :=plottype and enforce different requirements:
707+
- :scatter, :line, :area require both :=x and :=y
708+
- :bar, :histogram require :=x (y is optional)
709+
- nil (no plottype) allows incomplete layers for composition
710+
711+
This replaces the nested conditionals in validate-layer with declarative schemas."
712+
(m/schema
713+
[:multi {:dispatch :=plottype}
714+
715+
;; Scatter requires both x and y
716+
[:scatter
717+
[:merge
718+
BaseLayer
719+
[:map
720+
[:=plottype [:enum :scatter]]
721+
[:=x PositionalAesthetic]
722+
[:=y PositionalAesthetic]]]]
723+
724+
;; Line requires both x and y
725+
[:line
726+
[:merge
727+
BaseLayer
728+
[:map
729+
[:=plottype [:enum :line]]
730+
[:=x PositionalAesthetic]
731+
[:=y PositionalAesthetic]]]]
732+
733+
;; Bar requires x, y optional
734+
[:bar
735+
[:merge
736+
BaseLayer
737+
[:map
738+
[:=plottype [:enum :bar]]
739+
[:=x PositionalAesthetic]
740+
[:=y {:optional true} PositionalAesthetic]]]]
741+
742+
;; Histogram requires x, y optional
743+
[:histogram
744+
[:merge
745+
BaseLayer
746+
[:map
747+
[:=plottype [:enum :histogram]]
748+
[:=x PositionalAesthetic]
749+
[:=y {:optional true} PositionalAesthetic]]]]
750+
751+
;; Area requires both x and y
752+
[:area
753+
[:merge
754+
BaseLayer
755+
[:map
756+
[:=plottype [:enum :area]]
757+
[:=x PositionalAesthetic]
758+
[:=y PositionalAesthetic]]]]
759+
760+
;; Incomplete layer (no plottype) - for composition
761+
[nil
762+
[:merge
763+
BaseLayer
764+
[:map
765+
[:=plottype {:optional true} [:maybe nil?]]
766+
[:=x {:optional true} PositionalAesthetic]
767+
[:=y {:optional true} PositionalAesthetic]]]]]
768+
{:registry registry}))
769+
707770
(def Layers
708771
"Schema for one or more layers.
709772
@@ -784,65 +847,19 @@
784847
"Validate a layer with context-aware checks.
785848
786849
Performs:
787-
1. Schema validation (structure)
788-
2. Semantic validation (required fields for plottype)
789-
3. Data validation (columns exist)
850+
1. Schema validation (structure + plottype-specific requirements via :multi)
851+
2. Data column validation (columns exist) - runtime check
790852
791853
Returns nil if valid, error map if invalid."
792854
[layer]
793-
;; First check schema
855+
;; Schema validation now handles both structure AND plottype-specific requirements
794856
(or
795857
(when-let [schema-errors (validate Layer layer)]
796858
{:type :schema-error
797859
:errors schema-errors
798-
:message "Layer structure is invalid"})
799-
800-
;; Check plottype-specific requirements
801-
(let [plottype (:=plottype layer)]
802-
(when plottype
803-
(case plottype
804-
;; Scatter/line need x and y
805-
(:scatter :line)
806-
(when-not (and (:=x layer) (:=y layer))
807-
{:type :missing-required-aesthetic
808-
:plottype plottype
809-
:missing (cond
810-
(and (nil? (:=x layer)) (nil? (:=y layer))) [:=x :=y]
811-
(nil? (:=x layer)) [:=x]
812-
:else [:=y])
813-
:message (str plottype " plots require both :=x and :=y")})
814-
815-
;; Bar needs at least x
816-
:bar
817-
(when-not (:=x layer)
818-
{:type :missing-required-aesthetic
819-
:plottype plottype
820-
:missing [:=x]
821-
:message "Bar plots require :=x"})
822-
823-
;; Histogram needs just x
824-
:histogram
825-
(when-not (:=x layer)
826-
{:type :missing-required-aesthetic
827-
:plottype plottype
828-
:missing [:=x]
829-
:message "Histogram requires :=x"})
830-
831-
;; Area needs x and y
832-
:area
833-
(when-not (and (:=x layer) (:=y layer))
834-
{:type :missing-required-aesthetic
835-
:plottype plottype
836-
:missing (cond
837-
(and (nil? (:=x layer)) (nil? (:=y layer))) [:=x :=y]
838-
(nil? (:=x layer)) [:=x]
839-
:else [:=y])
840-
:message "Area plots require both :=x and :=y"})
841-
842-
;; Default - no specific requirements
843-
nil)))
844-
845-
;; Check data-related validations if data is present
860+
:message "Layer validation failed"})
861+
862+
;; Data column validation (runtime check - can't be done in schema)
846863
(when-let [data (:=data layer)]
847864
(let [column-keys (cond
848865
;; Tablecloth dataset
@@ -2383,7 +2400,6 @@ iris
23832400
(=* attrs-or-spec (scatter))
23842401
(let [result (merge {:=plottype :scatter}
23852402
(update-keys attrs-or-spec =key))]
2386-
(validate! Layer result)
23872403
{:=layers [result]})))
23882404
([spec attrs]
23892405
;; Threading-friendly: (-> spec (scatter {:alpha 0.5}))
@@ -2685,7 +2701,6 @@ iris
26852701
([]
26862702
(let [result {:=transformation :linear
26872703
:=plottype :line}]
2688-
(validate! Layer result)
26892704
{:=layers [result]}))
26902705
([spec-or-data]
26912706
(let [spec (if (plot-spec? spec-or-data)
@@ -2966,7 +2981,6 @@ iris
29662981
:=plottype :bar
29672982
:=bins :sturges}
29682983
(update-keys opts-or-spec =key))]
2969-
(validate! Layer result)
29702984
{:=layers [result]})))
29712985
([spec opts]
29722986
(=* spec (histogram opts))))

0 commit comments

Comments
 (0)