Skip to main content

graphcal_compiler/syntax/ast/
plot_props.rs

1//! Typed registry of plot, mark, and figure/layer properties.
2//!
3//! The single source of truth for which property names exist on each
4//! plot-family declaration and what value type each expects. Validation
5//! (TIR check), evaluation, and rendering all dispatch on these enums —
6//! never on raw property-name strings; `from_name` is the only place the
7//! source spelling crosses into the typed core.
8
9/// Expected value type of a plot-family property.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PlotPropertyType {
12    /// A string literal (e.g. `title: "Thrust"`).
13    String,
14    /// A dimensionless number.
15    Number,
16    /// A dimensionless number that must be strictly positive
17    /// (e.g. `width`, `height`); positivity is value-dependent and
18    /// checked at evaluation time.
19    PositiveNumber,
20    /// A boolean (e.g. `filled: true`).
21    Bool,
22}
23
24impl PlotPropertyType {
25    /// Human-readable expectation for diagnostics.
26    #[must_use]
27    pub const fn describe(self) -> &'static str {
28        match self {
29            Self::String => "a string literal",
30            Self::Number => "a dimensionless number",
31            Self::PositiveNumber => "a positive dimensionless number",
32            Self::Bool => "a boolean",
33        }
34    }
35}
36
37/// A mark-level property (style applied to the mark in a plot).
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub enum MarkProperty {
40    StrokeWidth,
41    Opacity,
42    Size,
43    Color,
44    Filled,
45    Interpolate,
46}
47
48impl MarkProperty {
49    /// Every mark property, for diagnostics listing the valid set.
50    pub const ALL: [Self; 6] = [
51        Self::StrokeWidth,
52        Self::Opacity,
53        Self::Size,
54        Self::Color,
55        Self::Filled,
56        Self::Interpolate,
57    ];
58
59    /// Parse a mark property from its source-level name.
60    #[must_use]
61    pub fn from_name(s: &str) -> Option<Self> {
62        Self::ALL.into_iter().find(|p| p.name() == s)
63    }
64
65    /// The source-level property name.
66    #[must_use]
67    pub const fn name(self) -> &'static str {
68        match self {
69            Self::StrokeWidth => "stroke_width",
70            Self::Opacity => "opacity",
71            Self::Size => "size",
72            Self::Color => "color",
73            Self::Filled => "filled",
74            Self::Interpolate => "interpolate",
75        }
76    }
77
78    /// The Vega-Lite camelCase property name.
79    #[must_use]
80    pub const fn vega_name(self) -> &'static str {
81        match self {
82            Self::StrokeWidth => "strokeWidth",
83            Self::Opacity => "opacity",
84            Self::Size => "size",
85            Self::Color => "color",
86            Self::Filled => "filled",
87            Self::Interpolate => "interpolate",
88        }
89    }
90
91    /// The value type this property expects.
92    #[must_use]
93    pub const fn value_type(self) -> PlotPropertyType {
94        match self {
95            Self::StrokeWidth | Self::Opacity | Self::Size => PlotPropertyType::Number,
96            Self::Color | Self::Interpolate => PlotPropertyType::String,
97            Self::Filled => PlotPropertyType::Bool,
98        }
99    }
100}
101
102/// A plot-level property.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
104pub enum PlotProperty {
105    Title,
106    Width,
107    Height,
108    XLabel,
109    YLabel,
110}
111
112impl PlotProperty {
113    /// Every plot property, for diagnostics listing the valid set.
114    pub const ALL: [Self; 5] = [
115        Self::Title,
116        Self::Width,
117        Self::Height,
118        Self::XLabel,
119        Self::YLabel,
120    ];
121
122    /// Parse a plot property from its source-level name.
123    #[must_use]
124    pub fn from_name(s: &str) -> Option<Self> {
125        Self::ALL.into_iter().find(|p| p.name() == s)
126    }
127
128    /// The source-level property name.
129    #[must_use]
130    pub const fn name(self) -> &'static str {
131        match self {
132            Self::Title => "title",
133            Self::Width => "width",
134            Self::Height => "height",
135            Self::XLabel => "x_label",
136            Self::YLabel => "y_label",
137        }
138    }
139
140    /// The value type this property expects.
141    #[must_use]
142    pub const fn value_type(self) -> PlotPropertyType {
143        match self {
144            Self::Title | Self::XLabel | Self::YLabel => PlotPropertyType::String,
145            Self::Width | Self::Height => PlotPropertyType::PositiveNumber,
146        }
147    }
148}
149
150/// A figure/layer-level property.
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub enum CompositionProperty {
153    Title,
154    Width,
155    Height,
156}
157
158impl CompositionProperty {
159    /// Every composition property, for diagnostics listing the valid set.
160    pub const ALL: [Self; 3] = [Self::Title, Self::Width, Self::Height];
161
162    /// Parse a composition property from its source-level name.
163    #[must_use]
164    pub fn from_name(s: &str) -> Option<Self> {
165        Self::ALL.into_iter().find(|p| p.name() == s)
166    }
167
168    /// The source-level property name.
169    #[must_use]
170    pub const fn name(self) -> &'static str {
171        match self {
172            Self::Title => "title",
173            Self::Width => "width",
174            Self::Height => "height",
175        }
176    }
177
178    /// The value type this property expects.
179    #[must_use]
180    pub const fn value_type(self) -> PlotPropertyType {
181        match self {
182            Self::Title => PlotPropertyType::String,
183            Self::Width | Self::Height => PlotPropertyType::PositiveNumber,
184        }
185    }
186
187    /// Whether the property is honored on `figure` declarations.
188    ///
189    /// Figures render as Vega-Lite `hconcat`, which has no top-level
190    /// width/height — only `title` applies; sizes belong on the
191    /// constituent plots (or on layers).
192    #[must_use]
193    pub const fn applies_to_figure(self) -> bool {
194        matches!(self, Self::Title)
195    }
196}