Skip to content

Commit c729b73

Browse files
committed
add possibility to specifiy only one value for axis range
Signed-off-by: Andrei Gherghescu <[email protected]>
1 parent 6b90b19 commit c729b73

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

plotly/src/layout/axis.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::common::{
77
TickFormatStop, TickMode, Title,
88
};
99
use crate::layout::RangeBreak;
10-
use crate::private::NumOrStringCollection;
10+
use crate::private::{NumOrStringCollection, AxisRange};
1111

1212
#[derive(Serialize, Debug, Clone)]
1313
#[serde(rename_all = "lowercase")]
@@ -309,7 +309,7 @@ pub struct Axis {
309309
range_breaks: Option<Vec<RangeBreak>>,
310310
#[serde(rename = "rangemode")]
311311
range_mode: Option<RangeMode>,
312-
range: Option<NumOrStringCollection>,
312+
range: Option<AxisRange>,
313313
#[serde(rename = "fixedrange")]
314314
fixed_range: Option<bool>,
315315
constrain: Option<AxisConstrain>,
@@ -715,7 +715,7 @@ mod tests {
715715
.type_(AxisType::Date)
716716
.auto_range(false)
717717
.range_mode(RangeMode::NonNegative)
718-
.range(vec![2.0])
718+
.range(AxisRange::from(vec![2.0]))
719719
.fixed_range(true)
720720
.constrain(AxisConstrain::Range)
721721
.constrain_toward(ConstrainDirection::Middle)
@@ -845,4 +845,52 @@ mod tests {
845845

846846
assert_eq!(to_value(axis).unwrap(), expected);
847847
}
848+
849+
#[test]
850+
fn serialize_axis_range_min_only() {
851+
let axis = Axis::new()
852+
.range(AxisRange::min_only(5.0));
853+
854+
let expected = json!({
855+
"range": [5.0, null]
856+
});
857+
858+
assert_eq!(to_value(axis).unwrap(), expected);
859+
}
860+
861+
#[test]
862+
fn serialize_axis_range_max_only() {
863+
let axis = Axis::new()
864+
.range(AxisRange::max_only(10.0));
865+
866+
let expected = json!({
867+
"range": [null, 10.0]
868+
});
869+
870+
assert_eq!(to_value(axis).unwrap(), expected);
871+
}
872+
873+
#[test]
874+
fn serialize_axis_range_both() {
875+
let axis = Axis::new()
876+
.range(AxisRange::both(1.0, 5.0));
877+
878+
let expected = json!({
879+
"range": [1.0, 5.0]
880+
});
881+
882+
assert_eq!(to_value(axis).unwrap(), expected);
883+
}
884+
885+
#[test]
886+
fn serialize_axis_range_with_strings() {
887+
let axis = Axis::new()
888+
.range(AxisRange::min_only("2020-01-01"));
889+
890+
let expected = json!({
891+
"range": ["2020-01-01", null]
892+
});
893+
894+
assert_eq!(to_value(axis).unwrap(), expected);
895+
}
848896
}

plotly/src/private.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,67 @@ where
125125
traces
126126
}
127127

128+
#[derive(Serialize, Clone, Debug, PartialEq)]
129+
pub struct AxisRange(pub Vec<Option<NumOrString>>);
130+
131+
impl AxisRange {
132+
/// Create a new axis range with two optional values (min, max)
133+
pub fn new(min: Option<NumOrString>, max: Option<NumOrString>) -> Self {
134+
Self(vec![min, max])
135+
}
136+
137+
/// Create a range with only a lower bound (min, None)
138+
pub fn min_only(min: impl Into<NumOrString>) -> Self {
139+
Self(vec![Some(min.into()), None])
140+
}
141+
142+
/// Create a range with only an upper bound (None, max)
143+
pub fn max_only(max: impl Into<NumOrString>) -> Self {
144+
Self(vec![None, Some(max.into())])
145+
}
146+
147+
/// Create a range with both bounds
148+
pub fn both(min: impl Into<NumOrString>, max: impl Into<NumOrString>) -> Self {
149+
Self(vec![Some(min.into()), Some(max.into())])
150+
}
151+
}
152+
153+
impl From<Vec<Option<NumOrString>>> for AxisRange {
154+
fn from(values: Vec<Option<NumOrString>>) -> Self {
155+
Self(values)
156+
}
157+
}
158+
159+
impl From<Vec<NumOrString>> for AxisRange {
160+
fn from(values: Vec<NumOrString>) -> Self {
161+
Self(values.into_iter().map(Some).collect())
162+
}
163+
}
164+
165+
impl From<Vec<f64>> for AxisRange {
166+
fn from(values: Vec<f64>) -> Self {
167+
Self(values.into_iter().map(|v| Some(NumOrString::F(v))).collect())
168+
}
169+
}
170+
171+
impl From<Vec<i64>> for AxisRange {
172+
fn from(values: Vec<i64>) -> Self {
173+
Self(values.into_iter().map(|v| Some(NumOrString::I(v))).collect())
174+
}
175+
}
176+
177+
impl From<Vec<String>> for AxisRange {
178+
fn from(values: Vec<String>) -> Self {
179+
Self(values.into_iter().map(|v| Some(NumOrString::S(v))).collect())
180+
}
181+
}
182+
183+
impl From<Vec<&str>> for AxisRange {
184+
fn from(values: Vec<&str>) -> Self {
185+
Self(values.into_iter().map(|v| Some(NumOrString::S(v.to_string()))).collect())
186+
}
187+
}
188+
128189
#[cfg(test)]
129190
mod tests {
130191
use serde_json::{json, to_value};

0 commit comments

Comments
 (0)