Skip to content

Commit 8e7d9fa

Browse files
wip: LayoutPolar
1 parent fe9e950 commit 8e7d9fa

File tree

2 files changed

+352
-1
lines changed

2 files changed

+352
-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: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
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+
impl LayoutPolar {
30+
pub fn new() -> Self {
31+
Default::default()
32+
}
33+
}
34+
35+
#[derive(Clone, Debug, FieldSetter, Serialize)]
36+
pub struct RadialAxis {
37+
visible: Option<bool>,
38+
#[serde(rename = "type")]
39+
axis_type: Option<RadialAxisType>,
40+
#[serde(rename = "autotypenumbers")]
41+
auto_type_numbers: Option<AutoTypeNumbers>,
42+
#[serde(rename = "autorangeoptions")]
43+
auto_range_options: Option<AutoRangeOptions>,
44+
#[serde(rename = "autorange")]
45+
auto_range: Option<AutoRange>,
46+
#[serde(rename = "rangemode")]
47+
range_mode: Option<RangeMode>,
48+
#[serde(rename = "minallowed")]
49+
min_allowed: Option<NumOrString>,
50+
#[serde(rename = "maxallowed")]
51+
max_allowed: Option<NumOrString>,
52+
range: Option<NumOrStringCollection>,
53+
#[serde(rename = "categoryorder")]
54+
category_order: Option<CategoryOrder>,
55+
#[serde(rename = "categoryarray")]
56+
category_array: Option<NumOrStringCollection>,
57+
angle: Option<f64>,
58+
#[serde(rename = "autotickangles")]
59+
auto_tick_angles: Option<Vec<f64>>,
60+
side: Option<PolarDirection>,
61+
title: Option<Title>,
62+
#[serde(rename = "hoverformat")]
63+
hover_format: Option<String>,
64+
#[serde(rename = "uirevision")]
65+
ui_revision: Option<NumOrString>,
66+
#[serde(flatten)]
67+
axis_attributes: Option<PolarAxisAttributes>,
68+
}
69+
70+
impl RadialAxis {
71+
pub fn new() -> Self {
72+
Default::default()
73+
}
74+
}
75+
76+
#[derive(Clone, Debug, FieldSetter, Serialize)]
77+
pub struct AngularAxis {
78+
visible: Option<bool>,
79+
#[serde(rename = "type")]
80+
axis_type: Option<AngularAxisType>,
81+
#[serde(rename = "autotypenumbers")]
82+
auto_type_numbers: Option<AutoTypeNumbers>,
83+
#[serde(rename = "categoryorder")]
84+
category_order: Option<CategoryOrder>,
85+
#[serde(rename = "categoryarray")]
86+
category_array: Option<NumOrStringCollection>,
87+
#[serde(rename = "thetaunit")]
88+
theta_unit: Option<ThetaUnit>,
89+
period: Option<usize>,
90+
direction: Option<PolarDirection>,
91+
rotation: Option<f64>,
92+
#[serde(rename = "hoverformat")]
93+
hover_format: Option<String>,
94+
#[serde(rename = "uirevision")]
95+
ui_revision: Option<NumOrString>,
96+
#[serde(flatten)]
97+
axis_attributes: Option<PolarAxisAttributes>,
98+
}
99+
100+
impl AngularAxis {
101+
pub fn new() -> Self {
102+
Default::default()
103+
}
104+
}
105+
106+
#[derive(Clone, Debug, FieldSetter, Serialize)]
107+
pub struct PolarAxisAttributes {
108+
color: Option<Box<dyn Color>>,
109+
#[serde(rename = "showline")]
110+
show_line: Option<bool>,
111+
#[serde(rename = "linecolor")]
112+
line_color: Option<Box<dyn Color>>,
113+
#[serde(rename = "linewidth")]
114+
line_width: Option<usize>,
115+
#[serde(rename = "showgrid")]
116+
show_grid: Option<bool>,
117+
#[serde(rename = "gridcolor")]
118+
grid_color: Option<Box<dyn Color>>,
119+
#[serde(rename = "gridwidth")]
120+
grid_width: Option<usize>,
121+
#[serde(rename = "griddash")]
122+
grid_dash: Option<DashType>,
123+
#[serde(flatten)]
124+
ticks: Option<PolarAxisTicks>,
125+
}
126+
127+
impl PolarAxisAttributes {
128+
pub fn new() -> Self {
129+
Default::default()
130+
}
131+
}
132+
133+
#[derive(Clone, Debug, FieldSetter, Serialize)]
134+
pub struct PolarAxisTicks {
135+
#[serde(rename = "tickmode")]
136+
tick_mode: Option<TickMode>,
137+
nticks: Option<usize>,
138+
tick0: Option<NumOrString>,
139+
dtick: Option<NumOrString>,
140+
#[serde(rename = "tickvals")]
141+
tick_values: Option<Vec<f64>>,
142+
#[serde(rename = "ticktext")]
143+
tick_text: Option<Vec<String>>,
144+
ticks: Option<Ticks>,
145+
#[serde(rename = "ticklen")]
146+
tick_length: Option<usize>,
147+
#[serde(rename = "tickwidth")]
148+
tick_width: Option<usize>,
149+
#[serde(rename = "tickcolor")]
150+
tick_color: Option<Box<dyn Color>>,
151+
#[serde(rename = "ticklabelstep")]
152+
tick_label_step: Option<NonZeroU8>,
153+
#[serde(rename = "showticklabels")]
154+
show_tick_labels: Option<bool>,
155+
#[serde(rename = "labelalias")]
156+
label_alias: Option<String>,
157+
#[serde(rename = "minorloglabels")]
158+
minor_log_labels: Option<MinorLogLabels>,
159+
#[serde(rename = "showtickprefix")]
160+
show_tick_prefix: Option<ArrayShow>,
161+
#[serde(rename = "tickprefix")]
162+
tick_prefix: Option<String>,
163+
#[serde(rename = "showticksuffix")]
164+
show_tick_suffix: Option<ArrayShow>,
165+
#[serde(rename = "ticksuffix")]
166+
tick_suffix: Option<String>,
167+
#[serde(rename = "showexponent")]
168+
show_exponent: Option<ArrayShow>,
169+
#[serde(rename = "exponentformat")]
170+
exponent_format: Option<ExponentFormat>,
171+
#[serde(rename = "minexponent")]
172+
min_exponent: Option<u8>,
173+
#[serde(rename = "separatethousands")]
174+
separate_thousands: Option<bool>,
175+
#[serde(rename = "tickfont")]
176+
tick_font: Option<Font>,
177+
#[serde(rename = "tickangle")]
178+
tick_angle: Option<f64>,
179+
#[serde(rename = "tickformat")]
180+
tick_format: Option<String>,
181+
#[serde(rename = "tickformatstops")]
182+
tick_format_stops: Option<TickFormatStop>,
183+
layer: Option<AxisLayer>,
184+
}
185+
186+
impl PolarAxisTicks {
187+
pub fn new() -> Self {
188+
Default::default()
189+
}
190+
}
191+
192+
#[derive(Clone, Debug, Serialize)]
193+
#[serde(rename_all = "lowercase")]
194+
pub enum AngularAxisType {
195+
#[serde(rename = "-")]
196+
Default,
197+
Linear,
198+
Category,
199+
}
200+
201+
#[derive(Clone, Debug, Serialize)]
202+
#[serde(rename_all = "lowercase")]
203+
pub enum AutoRange {
204+
Max,
205+
#[serde(rename = "max reversed")]
206+
MaxReversed,
207+
Min,
208+
#[serde(rename = "min reversed")]
209+
MinReversed,
210+
Reversed,
211+
#[serde(untagged)]
212+
Bool(bool),
213+
}
214+
215+
#[derive(Clone, Debug, FieldSetter, Serialize)]
216+
pub struct AutoRangeOptions {
217+
#[serde(rename = "minallowed")]
218+
min_allowed: Option<NumOrString>,
219+
#[serde(rename = "maxallowed")]
220+
max_allowed: Option<NumOrString>,
221+
#[serde(rename = "clipmin")]
222+
clip_min: Option<NumOrString>,
223+
#[serde(rename = "clipmax")]
224+
clip_max: Option<NumOrString>,
225+
include: Option<NumOrStringCollection>,
226+
}
227+
228+
// Perhaps move these enums to plotly/common
229+
#[derive(Clone, Debug, Serialize)]
230+
#[serde(rename_all = "lowercase")]
231+
pub enum AutoTypeNumbers {
232+
#[serde(rename = "convert types")]
233+
Convert,
234+
Strict,
235+
}
236+
237+
#[derive(Clone, Debug, Serialize)]
238+
pub enum AxisLayer {
239+
#[serde(rename = "above traces")]
240+
Above,
241+
#[serde(rename = "below traces")]
242+
Below,
243+
}
244+
245+
#[derive(Clone, Debug, Serialize)]
246+
#[serde(rename_all = "lowercase")]
247+
pub enum GridShape {
248+
Circular,
249+
Linear,
250+
}
251+
252+
#[derive(Clone, Debug, Serialize)]
253+
pub struct Hole(f64);
254+
255+
impl Hole {
256+
pub fn new(value: f64) -> Result<Self, Box<dyn std::error::Error>> {
257+
if (0.0..=1.0).contains(&value) {
258+
Ok(Self(value))
259+
} else {
260+
Err(format!("The value for a LayoutPolar angular axis Hole must be between 0.0 and 1.0. Given value: {value}").into())
261+
}
262+
}
263+
}
264+
265+
impl AsRef<f64> for Hole {
266+
fn as_ref(&self) -> &f64 {
267+
&self.0
268+
}
269+
}
270+
271+
impl Display for Hole {
272+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273+
write!(f, "{}", self.0)
274+
}
275+
}
276+
277+
#[derive(Clone, Debug, Serialize)]
278+
#[serde(rename_all = "lowercase")]
279+
pub enum MinorLogLabels {
280+
#[serde(rename = "small digits")]
281+
SmallDigits,
282+
Complete,
283+
None,
284+
}
285+
286+
#[derive(Clone, Debug, Serialize)]
287+
#[serde(rename_all = "lowercase")]
288+
pub enum PolarDirection {
289+
Clockwise,
290+
Counterclockwise,
291+
}
292+
293+
#[derive(Clone, Debug, Serialize)]
294+
#[serde(rename_all = "lowercase")]
295+
pub enum RadialAxisType {
296+
#[serde(rename = "-")]
297+
Default,
298+
Linear,
299+
Log,
300+
Date,
301+
Category,
302+
}
303+
304+
#[derive(Clone, Debug, Serialize)]
305+
#[serde(rename_all = "lowercase")]
306+
pub enum ThetaUnit {
307+
Degrees,
308+
Radians,
309+
}
310+
311+
#[cfg(test)]
312+
mod tests {
313+
use super::*;
314+
use crate::{common::Mode, Layout, Plot, ScatterPolar};
315+
316+
#[test]
317+
fn layout_polar() {
318+
let radial_ticks = PolarAxisTicks::new().tick_color("#ffff00");
319+
let angular_ticks = PolarAxisTicks::new().tick_color("#00ffff");
320+
321+
let radial_axis_attributes = PolarAxisAttributes::new()
322+
.color("#ffffff")
323+
.ticks(radial_ticks);
324+
325+
let angular_axis_attributes = PolarAxisAttributes::new().ticks(angular_ticks);
326+
let radial_axis = RadialAxis::new().axis_attributes(radial_axis_attributes);
327+
let angular_axis = AngularAxis::new().axis_attributes(angular_axis_attributes);
328+
329+
let layout_polar = LayoutPolar::new()
330+
.bg_color("#292d3e")
331+
.radial_axis(radial_axis)
332+
.angular_axis(angular_axis);
333+
334+
let layout = Layout::new().polar(layout_polar);
335+
336+
let first_trace = ScatterPolar::new(vec![0, 1, 2], vec![3, 4, 5])
337+
.name("First")
338+
.mode(Mode::Markers);
339+
340+
let second_trace = ScatterPolar::new(vec![6, 7, 8], vec![9, 10, 11])
341+
.name("Second")
342+
.mode(Mode::Markers);
343+
344+
let mut plot = Plot::new();
345+
plot.set_layout(layout);
346+
plot.add_traces(vec![first_trace, second_trace]);
347+
plot.show();
348+
}
349+
}

0 commit comments

Comments
 (0)