Skip to content

Commit f9ed4b3

Browse files
wip: LayoutPolar
1 parent fe9e950 commit f9ed4b3

File tree

2 files changed

+298
-1
lines changed

2 files changed

+298
-1
lines changed

plotly/src/layout/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod grid;
1919
mod legend;
2020
mod mapbox;
2121
mod modes;
22+
mod polar;
2223
mod rangebreaks;
2324
mod scene;
2425
mod shape;
@@ -42,6 +43,7 @@ pub use self::mapbox::{Center, Mapbox, MapboxStyle};
4243
pub use self::modes::{
4344
AspectMode, BarMode, BarNorm, BoxMode, ClickMode, UniformTextMode, ViolinMode, WaterfallMode,
4445
};
46+
pub use self::polar::LayoutPolar;
4547
pub use self::rangebreaks::RangeBreak;
4648
pub use self::scene::{
4749
AspectRatio, Camera, CameraCenter, DragMode, DragMode3D, Eye, HoverMode, LayoutScene,
@@ -330,7 +332,7 @@ pub struct LayoutFields {
330332
// ternary: Option<LayoutTernary>,
331333
scene: Option<LayoutScene>,
332334
geo: Option<LayoutGeo>,
333-
// polar: Option<LayoutPolar>,
335+
polar: Option<LayoutPolar>,
334336
annotations: Option<Vec<Annotation>>,
335337
shapes: Option<Vec<Shape>>,
336338
#[serde(rename = "newshape")]

plotly/src/layout/polar.rs

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
use std::{fmt::Display, num::NonZeroU8};
2+
3+
use plotly_derive::FieldSetter;
4+
use serde::Serialize;
5+
6+
use crate::{
7+
color::Color,
8+
common::{DashType, ExponentFormat, Font, TickFormatStop, TickMode, Ticks, Title},
9+
layout::{ArrayShow, CategoryOrder, RangeMode},
10+
private::{NumOrString, NumOrStringCollection},
11+
};
12+
13+
#[derive(Clone, Debug, FieldSetter, Serialize)]
14+
pub struct LayoutPolar {
15+
sector: Option<[f64; 2]>,
16+
hole: Option<Hole>,
17+
#[serde(rename = "bgcolor")]
18+
bg_color: Option<Box<dyn Color>>,
19+
#[serde(rename = "radialaxis")]
20+
radial_axis: Option<RadialAxis>,
21+
#[serde(rename = "angularaxis")]
22+
angular_axis: Option<AngularAxis>,
23+
#[serde(rename = "gridshape")]
24+
grid_shape: Option<GridShape>,
25+
#[serde(rename = "uirevision")]
26+
ui_revision: Option<NumOrString>,
27+
}
28+
29+
// There is partial overlap with [`Axis`](crate::layout::Axis), for both
30+
// [`PolarAxis`] and [`PolarAxisTicks`] so it may be possible to factor that
31+
// out.
32+
#[derive(Clone, Debug, FieldSetter, Serialize)]
33+
pub struct PolarAxis {
34+
color: Option<Box<dyn Color>>,
35+
#[serde(rename = "showline")]
36+
show_line: Option<bool>,
37+
#[serde(rename = "linecolor")]
38+
line_color: Option<Box<dyn Color>>,
39+
#[serde(rename = "linewidth")]
40+
line_width: Option<usize>,
41+
#[serde(rename = "showgrid")]
42+
show_grid: Option<bool>,
43+
#[serde(rename = "gridcolor")]
44+
grid_color: Option<Box<dyn Color>>,
45+
#[serde(rename = "gridwidth")]
46+
grid_width: Option<usize>,
47+
#[serde(rename = "griddash")]
48+
grid_dash: Option<DashType>,
49+
#[serde(flatten)]
50+
ticks: Option<PolarAxisTicks>,
51+
}
52+
53+
#[derive(Clone, Debug, FieldSetter, Serialize)]
54+
pub struct PolarAxisTicks {
55+
#[serde(rename = "tickmode")]
56+
tick_mode: Option<TickMode>,
57+
nticks: Option<usize>,
58+
tick0: Option<NumOrString>,
59+
dtick: Option<NumOrString>,
60+
#[serde(rename = "tickvals")]
61+
tick_values: Option<Vec<f64>>,
62+
#[serde(rename = "ticktext")]
63+
tick_text: Option<Vec<String>>,
64+
ticks: Option<Ticks>,
65+
#[serde(rename = "ticklen")]
66+
tick_length: Option<usize>,
67+
#[serde(rename = "tickwidth")]
68+
tick_width: Option<usize>,
69+
#[serde(rename = "tickcolor")]
70+
tick_color: Option<Box<dyn Color>>,
71+
#[serde(rename = "ticklabelstep")]
72+
tick_label_step: Option<NonZeroU8>,
73+
#[serde(rename = "showticklabels")]
74+
show_tick_labels: Option<bool>,
75+
#[serde(rename = "labelalias")]
76+
label_alias: Option<String>,
77+
#[serde(rename = "minorloglabels")]
78+
minor_log_labels: Option<MinorLogLabels>,
79+
#[serde(rename = "showtickprefix")]
80+
show_tick_prefix: Option<ArrayShow>,
81+
#[serde(rename = "tickprefix")]
82+
tick_prefix: Option<String>,
83+
#[serde(rename = "showticksuffix")]
84+
show_tick_suffix: Option<ArrayShow>,
85+
#[serde(rename = "ticksuffix")]
86+
tick_suffix: Option<String>,
87+
#[serde(rename = "showexponent")]
88+
show_exponent: Option<ArrayShow>,
89+
#[serde(rename = "exponentformat")]
90+
exponent_format: Option<ExponentFormat>,
91+
#[serde(rename = "minexponent")]
92+
min_exponent: Option<u8>,
93+
#[serde(rename = "separatethousands")]
94+
separate_thousands: Option<bool>,
95+
#[serde(rename = "tickfont")]
96+
tick_font: Option<Font>,
97+
#[serde(rename = "tickangle")]
98+
tick_angle: Option<f64>,
99+
#[serde(rename = "tickformat")]
100+
tick_format: Option<String>,
101+
#[serde(rename = "tickformatstops")]
102+
tick_format_stops: Option<TickFormatStop>,
103+
layer: Option<AxisLayer>,
104+
}
105+
106+
#[derive(Clone, Debug, FieldSetter, Serialize)]
107+
pub struct RadialAxis {
108+
visible: Option<bool>,
109+
// Should this have `#[field_setter(skip)]`?
110+
#[serde(rename = "type")]
111+
axis_type: Option<RadialAxisType>,
112+
#[serde(rename = "autotypenumbers")]
113+
auto_type_numbers: Option<AutoTypeNumbers>,
114+
#[serde(rename = "autorangeoptions")]
115+
auto_range_options: Option<AutoRangeOptions>,
116+
#[serde(rename = "autorange")]
117+
auto_range: Option<AutoRange>,
118+
#[serde(rename = "rangemode")]
119+
range_mode: Option<RangeMode>,
120+
#[serde(rename = "minallowed")]
121+
min_allowed: Option<NumOrString>,
122+
#[serde(rename = "maxallowed")]
123+
max_allowed: Option<NumOrString>,
124+
range: Option<NumOrStringCollection>,
125+
#[serde(rename = "categoryorder")]
126+
category_order: Option<CategoryOrder>,
127+
#[serde(rename = "categoryarray")]
128+
category_array: Option<NumOrStringCollection>,
129+
angle: Option<f64>,
130+
#[serde(rename = "autotickangles")]
131+
auto_tick_angles: Option<Vec<f64>>,
132+
side: Option<PolarDirection>,
133+
title: Option<Title>,
134+
#[serde(rename = "hoverformat")]
135+
hover_format: Option<String>,
136+
#[serde(rename = "uirevision")]
137+
ui_revision: Option<NumOrString>,
138+
#[serde(flatten)]
139+
axis_attributes: Option<PolarAxis>,
140+
}
141+
142+
#[derive(Clone, Debug, FieldSetter, Serialize)]
143+
pub struct AngularAxis {
144+
visible: Option<bool>,
145+
// Should this have `#[field_setter(skip)]`?
146+
#[serde(rename = "type")]
147+
axis_type: Option<AngularAxisType>,
148+
#[serde(rename = "autotypenumbers")]
149+
auto_type_numbers: Option<AutoTypeNumbers>,
150+
#[serde(rename = "categoryorder")]
151+
category_order: Option<CategoryOrder>,
152+
#[serde(rename = "categoryarray")]
153+
category_array: Option<NumOrStringCollection>,
154+
#[serde(rename = "thetaunit")]
155+
theta_unit: Option<ThetaUnit>,
156+
period: Option<usize>,
157+
direction: Option<PolarDirection>,
158+
rotation: Option<f64>,
159+
#[serde(rename = "hoverformat")]
160+
hover_format: Option<String>,
161+
#[serde(rename = "uirevision")]
162+
ui_revision: Option<NumOrString>,
163+
#[serde(flatten)]
164+
axis_attributes: Option<PolarAxis>,
165+
}
166+
167+
#[derive(Clone, Debug, Serialize)]
168+
#[serde(rename_all = "lowercase")]
169+
pub enum AngularAxisType {
170+
#[serde(rename = "-")]
171+
Default,
172+
Linear,
173+
Category,
174+
}
175+
176+
#[derive(Clone, Debug, Serialize)]
177+
#[serde(rename_all = "lowercase")]
178+
pub enum AutoRange {
179+
Max,
180+
#[serde(rename = "max reversed")]
181+
MaxReversed,
182+
Min,
183+
#[serde(rename = "min reversed")]
184+
MinReversed,
185+
Reversed,
186+
#[serde(untagged)]
187+
Bool(bool),
188+
}
189+
190+
#[derive(Clone, Debug, FieldSetter, Serialize)]
191+
pub struct AutoRangeOptions {
192+
#[serde(rename = "minallowed")]
193+
min_allowed: Option<NumOrString>,
194+
#[serde(rename = "maxallowed")]
195+
max_allowed: Option<NumOrString>,
196+
#[serde(rename = "clipmin")]
197+
clip_min: Option<NumOrString>,
198+
#[serde(rename = "clipmax")]
199+
clip_max: Option<NumOrString>,
200+
include: Option<NumOrStringCollection>,
201+
}
202+
203+
// Perhaps move these enums to plotly/common
204+
#[derive(Clone, Debug, Serialize)]
205+
#[serde(rename_all = "lowercase")]
206+
pub enum AutoTypeNumbers {
207+
#[serde(rename = "convert types")]
208+
Convert,
209+
Strict,
210+
}
211+
212+
#[derive(Clone, Debug, Serialize)]
213+
pub enum AxisLayer {
214+
#[serde(rename = "above traces")]
215+
Above,
216+
#[serde(rename = "below traces")]
217+
Below,
218+
}
219+
220+
#[derive(Clone, Debug, Serialize)]
221+
#[serde(rename_all = "lowercase")]
222+
pub enum GridShape {
223+
Circular,
224+
Linear,
225+
}
226+
227+
#[derive(Clone, Debug, Serialize)]
228+
pub struct Hole(f64);
229+
230+
impl Hole {
231+
// Just following the boxed error pattern I see elsewhere in the repo. Happy to
232+
// make a custom error type if that's preferred.
233+
pub fn new(value: f64) -> Result<Self, Box<dyn std::error::Error>> {
234+
if (0.0..=1.0).contains(&value) {
235+
Ok(Self(value))
236+
} else {
237+
Err(format!("The value for a LayoutPolar angular axis Hole must be between 0.0 and 1.0. Given value: {value}").into())
238+
}
239+
}
240+
}
241+
242+
impl AsRef<f64> for Hole {
243+
fn as_ref(&self) -> &f64 {
244+
&self.0
245+
}
246+
}
247+
248+
impl Display for Hole {
249+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250+
write!(f, "{}", self.0)
251+
}
252+
}
253+
254+
#[derive(Clone, Debug, Serialize)]
255+
#[serde(rename_all = "lowercase")]
256+
pub enum MinorLogLabels {
257+
#[serde(rename = "small digits")]
258+
SmallDigits,
259+
Complete,
260+
None,
261+
}
262+
263+
#[derive(Clone, Debug, Serialize)]
264+
#[serde(rename_all = "lowercase")]
265+
pub enum PolarDirection {
266+
Clockwise,
267+
Counterclockwise,
268+
}
269+
270+
#[derive(Clone, Debug, Serialize)]
271+
#[serde(rename_all = "lowercase")]
272+
pub enum RadialAxisType {
273+
#[serde(rename = "-")]
274+
Default,
275+
Linear,
276+
Log,
277+
Date,
278+
Category,
279+
}
280+
281+
#[derive(Clone, Debug, Serialize)]
282+
#[serde(rename_all = "lowercase")]
283+
pub enum ThetaUnit {
284+
Degrees,
285+
Radians,
286+
}
287+
288+
mod tests {
289+
use super::*;
290+
291+
#[test]
292+
fn serialize_layout_polar() {
293+
todo!()
294+
}
295+
}

0 commit comments

Comments
 (0)