Skip to content

Commit c62820d

Browse files
jqnatividadclaude
andcommitted
feat(indicator): expand GaugeAxis to full gauge.axis schema coverage
Replace the minimal GaugeAxis (range + a few ticks) with the complete set of 27 attributes plotly.js exposes on indicator.gauge.axis: tick mode/placement (tickmode/nticks/tick0/dtick/tickvals/ticktext), tick styling (ticks/ticklen/tickwidth/tickcolor/tickangle/tickfont), label controls (showticklabels/tickprefix/ticksuffix + their ArrayShow flags), exponent and number formatting (showexponent/exponentformat/minexponent/separatethousands/ tickformat/tickformatstops/ticklabelstep), and labelalias. Reuses existing enums (common::TickMode/ExponentFormat/TickFormatStop, layout::ArrayShow/TicksDirection) rather than layout::Axis, which carries ~70 attributes (grid lines, spikes, range sliders, subplot anchoring) that a gauge axis does not support and would silently serialize as ignored JSON. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 078b139 commit c62820d

1 file changed

Lines changed: 165 additions & 9 deletions

File tree

plotly/src/traces/indicator.rs

Lines changed: 165 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Indicator trace
22
3+
use std::collections::HashMap;
4+
35
use plotly_derive::FieldSetter;
46
use serde::Serialize;
57

68
use crate::color::Color;
9+
use crate::layout::{ArrayShow, TicksDirection};
710
use crate::private::{NumOrString, NumOrStringCollection};
811
use crate::{
9-
common::{Domain, Font, Line, PlotType},
12+
common::{Domain, ExponentFormat, Font, Line, PlotType, TickFormatStop, TickMode},
1013
Trace,
1114
};
1215

@@ -151,34 +154,108 @@ impl Delta {
151154

152155
/// Configures the axis of a gauge (its range and ticks).
153156
///
154-
/// This is a minimal subset of a full Cartesian axis, exposing only the
155-
/// attributes relevant to gauges.
157+
/// This mirrors the subset of a Cartesian axis that Plotly.js exposes on
158+
/// `indicator.gauge.axis`: a numeric `range` plus the full tick-styling
159+
/// machinery. It is intentionally distinct from [`crate::layout::Axis`], which
160+
/// carries many attributes (grid lines, spikes, range sliders, subplot
161+
/// anchoring, …) that a gauge axis does not support.
156162
#[serde_with::skip_serializing_none]
157163
#[derive(Serialize, Clone, Debug, FieldSetter)]
158164
pub struct GaugeAxis {
159165
/// Sets the range of the gauge axis.
160166
range: Option<[f64; 2]>,
161167
/// Determines whether or not the gauge axis is visible.
162168
visible: Option<bool>,
163-
/// Sets the tick mode of the gauge axis (e.g. `"auto"`, `"linear"`,
164-
/// `"array"`).
169+
/// Sets the tick mode of the gauge axis.
165170
#[serde(rename = "tickmode")]
166-
tick_mode: Option<String>,
167-
/// Sets the number of ticks.
171+
tick_mode: Option<TickMode>,
172+
/// Sets the number of ticks (with `tick_mode` [`TickMode::Auto`]).
168173
#[serde(rename = "nticks")]
169174
n_ticks: Option<usize>,
170-
/// Sets the values at which ticks are drawn (with `tick_mode` `"array"`).
175+
/// Sets the placement of the first tick (with `tick_mode`
176+
/// [`TickMode::Linear`]).
177+
tick0: Option<f64>,
178+
/// Sets the step between ticks (with `tick_mode` [`TickMode::Linear`]).
179+
dtick: Option<f64>,
180+
/// Sets the values at which ticks are drawn (with `tick_mode`
181+
/// [`TickMode::Array`]).
171182
#[serde(rename = "tickvals")]
172183
tick_vals: Option<Vec<f64>>,
173-
/// Sets the text displayed at the tick values (with `tick_mode` `"array"`).
184+
/// Sets the text displayed at the tick values (with `tick_mode`
185+
/// [`TickMode::Array`]).
174186
#[serde(rename = "ticktext")]
175187
tick_text: Option<Vec<String>>,
188+
/// Determines whether ticks are drawn inside or outside the axis line.
189+
ticks: Option<TicksDirection>,
190+
/// Sets the tick length (in px).
191+
#[serde(rename = "ticklen")]
192+
tick_length: Option<usize>,
193+
/// Sets the tick width (in px).
194+
#[serde(rename = "tickwidth")]
195+
tick_width: Option<usize>,
196+
/// Sets the tick color.
197+
#[serde(rename = "tickcolor")]
198+
#[field_setter(skip)]
199+
tick_color: Option<Box<dyn Color>>,
200+
/// Determines whether or not the tick labels are drawn.
201+
#[serde(rename = "showticklabels")]
202+
show_tick_labels: Option<bool>,
203+
/// Sets the font used for the tick labels.
204+
#[serde(rename = "tickfont")]
205+
tick_font: Option<Font>,
206+
/// Sets the angle (in degrees) of the tick labels.
207+
#[serde(rename = "tickangle")]
208+
tick_angle: Option<f64>,
209+
/// Sets a prefix appearing before each tick label.
210+
#[serde(rename = "tickprefix")]
211+
tick_prefix: Option<String>,
212+
/// Determines on which ticks the prefix is shown.
213+
#[serde(rename = "showtickprefix")]
214+
show_tick_prefix: Option<ArrayShow>,
215+
/// Sets a suffix appearing after each tick label.
216+
#[serde(rename = "ticksuffix")]
217+
tick_suffix: Option<String>,
218+
/// Determines on which ticks the suffix is shown.
219+
#[serde(rename = "showticksuffix")]
220+
show_tick_suffix: Option<ArrayShow>,
221+
/// Determines on which ticks the exponent is shown.
222+
#[serde(rename = "showexponent")]
223+
show_exponent: Option<ArrayShow>,
224+
/// Determines the formatting of exponents.
225+
#[serde(rename = "exponentformat")]
226+
exponent_format: Option<ExponentFormat>,
227+
/// Sets the smallest exponent that gets a suffix/prefix (with
228+
/// `exponent_format` [`ExponentFormat::SI`]/[`ExponentFormat::B`]).
229+
#[serde(rename = "minexponent")]
230+
min_exponent: Option<usize>,
231+
/// Determines whether thousands are separated (e.g. `1,000,000`).
232+
#[serde(rename = "separatethousands")]
233+
separate_thousands: Option<bool>,
234+
/// Sets the tick label formatting rule using d3 formatting
235+
/// mini-languages.
236+
#[serde(rename = "tickformat")]
237+
tick_format: Option<String>,
238+
/// Sets conditional tick-label formatting rules by range.
239+
#[serde(rename = "tickformatstops")]
240+
tick_format_stops: Option<Vec<TickFormatStop>>,
241+
/// Sets the spacing between tick labels: every n-th label is shown.
242+
#[serde(rename = "ticklabelstep")]
243+
tick_label_step: Option<usize>,
244+
/// Replaces specific tick labels with the provided aliases.
245+
#[serde(rename = "labelalias")]
246+
label_alias: Option<HashMap<String, String>>,
176247
}
177248

178249
impl GaugeAxis {
179250
pub fn new() -> Self {
180251
Default::default()
181252
}
253+
254+
/// Sets the tick color.
255+
pub fn tick_color<C: Color>(mut self, color: C) -> Self {
256+
self.tick_color = Some(Box::new(color));
257+
self
258+
}
182259
}
183260

184261
/// Configures the value bar drawn inside a gauge.
@@ -495,6 +572,85 @@ mod tests {
495572
assert_eq!(to_value(trace).unwrap(), expected);
496573
}
497574

575+
#[test]
576+
fn serialize_gauge_axis() {
577+
let axis = GaugeAxis::new()
578+
.range([0.0, 100.0])
579+
.visible(true)
580+
.tick_mode(TickMode::Array)
581+
.n_ticks(5)
582+
.tick0(0.0)
583+
.dtick(10.0)
584+
.tick_vals(vec![0.0, 50.0, 100.0])
585+
.tick_text(vec![
586+
"low".to_string(),
587+
"mid".to_string(),
588+
"high".to_string(),
589+
])
590+
.ticks(TicksDirection::Outside)
591+
.tick_length(8)
592+
.tick_width(2)
593+
.tick_color("gray")
594+
.show_tick_labels(true)
595+
.tick_font(Font::new())
596+
.tick_angle(45.0)
597+
.tick_prefix("$")
598+
.show_tick_prefix(ArrayShow::All)
599+
.tick_suffix("k")
600+
.show_tick_suffix(ArrayShow::Last)
601+
.show_exponent(ArrayShow::First)
602+
.exponent_format(ExponentFormat::SI)
603+
.min_exponent(3)
604+
.separate_thousands(true)
605+
.tick_format(".2f")
606+
.tick_label_step(2);
607+
608+
let expected = json!({
609+
"range": [0.0, 100.0],
610+
"visible": true,
611+
"tickmode": "array",
612+
"nticks": 5,
613+
"tick0": 0.0,
614+
"dtick": 10.0,
615+
"tickvals": [0.0, 50.0, 100.0],
616+
"ticktext": ["low", "mid", "high"],
617+
"ticks": "outside",
618+
"ticklen": 8,
619+
"tickwidth": 2,
620+
"tickcolor": "gray",
621+
"showticklabels": true,
622+
"tickfont": {},
623+
"tickangle": 45.0,
624+
"tickprefix": "$",
625+
"showtickprefix": "all",
626+
"ticksuffix": "k",
627+
"showticksuffix": "last",
628+
"showexponent": "first",
629+
"exponentformat": "SI",
630+
"minexponent": 3,
631+
"separatethousands": true,
632+
"tickformat": ".2f",
633+
"ticklabelstep": 2,
634+
});
635+
636+
assert_eq!(to_value(axis).unwrap(), expected);
637+
}
638+
639+
#[test]
640+
fn serialize_gauge_axis_label_alias_and_format_stops() {
641+
use std::collections::HashMap;
642+
643+
let mut alias = HashMap::new();
644+
alias.insert("0".to_string(), "min".to_string());
645+
let axis = GaugeAxis::new()
646+
.label_alias(alias)
647+
.tick_format_stops(vec![TickFormatStop::new()]);
648+
649+
let v = to_value(axis).unwrap();
650+
assert_eq!(v["labelalias"], json!({"0": "min"}));
651+
assert!(v["tickformatstops"].is_array());
652+
}
653+
498654
#[test]
499655
fn serialize_indicator_legend_and_uirevision() {
500656
let trace = Indicator::new(1.0)

0 commit comments

Comments
 (0)