1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Waterfall chart builder methods.
//!
//! Provides a fluent builder API for configuring [`WaterfallArtist`] instances.
//! Each method returns `&mut Self`, allowing calls to be chained together
//! for concise, readable chart construction.
use crate::artist::WaterfallArtist;
use crate::primitives::Color;
impl WaterfallArtist {
/// Sets the color for bars representing positive (increasing) changes.
///
/// # Arguments
///
/// * `color` - The [`Color`] to fill increase bars with.
pub fn increase_color(&mut self, color: Color) -> &mut Self {
self.increase_color = color;
self
}
/// Sets the color for bars representing negative (decreasing) changes.
///
/// # Arguments
///
/// * `color` - The [`Color`] to fill decrease bars with.
pub fn decrease_color(&mut self, color: Color) -> &mut Self {
self.decrease_color = color;
self
}
/// Sets the color for total bars (bars drawn from zero).
///
/// # Arguments
///
/// * `color` - The [`Color`] to fill total bars with.
pub fn total_color(&mut self, color: Color) -> &mut Self {
self.total_color = color;
self
}
/// Enables or disables connector lines between consecutive bar tops.
///
/// When enabled (the default), thin horizontal lines are drawn from the
/// end of each bar to the start of the next, visually connecting the
/// running total across the chart.
///
/// # Arguments
///
/// * `enabled` - `true` to draw connector lines, `false` to hide them.
pub fn connector_lines(&mut self, enabled: bool) -> &mut Self {
self.connector_lines = enabled;
self
}
/// Enables or disables value labels displayed on each bar.
///
/// When enabled, each bar shows its change value (or total value for
/// total bars) as a text label positioned at the bar's top edge.
///
/// # Arguments
///
/// * `enabled` - `true` to show value labels, `false` to hide them.
pub fn show_values(&mut self, enabled: bool) -> &mut Self {
self.show_values = enabled;
self
}
/// Sets the bar width as a fraction of the category spacing (0.0 to 1.0).
///
/// Smaller values produce thinner bars with more whitespace between them,
/// while larger values make the bars wider. The value is clamped to the
/// range `[0.1, 1.0]` so that bars are never invisibly thin nor overlap
/// their neighbours.
///
/// # Arguments
///
/// * `width` - The fraction of available category space each bar should occupy.
pub fn bar_width(&mut self, width: f64) -> &mut Self {
self.bar_width = width.clamp(0.1, 1.0);
self
}
/// Sets the legend label.
///
/// When a legend is displayed on the figure, this label will appear
/// next to the color swatch for this waterfall series. Passing a new
/// value overwrites any previously set label.
///
/// # Arguments
///
/// * `label` - A string slice that will be stored as the legend entry.
pub fn label(&mut self, label: &str) -> &mut Self {
self.label = Some(label.to_string());
self
}
/// Marks the given bar indices as "total" bars.
///
/// Total bars are drawn from zero to the current cumulative sum, rather
/// than showing the incremental change. Typically the first and/or last
/// bar in a waterfall chart are marked as totals.
///
/// # Arguments
///
/// * `indices` - A slice of zero-based category indices to treat as totals.
pub fn total(&mut self, indices: &[usize]) -> &mut Self {
self.total_indices = indices.to_vec();
self
}
/// Sets the opacity.
///
/// The value is clamped to the range `[0.0, 1.0]`, where `0.0` is fully
/// transparent and `1.0` is fully opaque.
///
/// # Arguments
///
/// * `alpha` - The desired opacity level.
pub fn alpha(&mut self, alpha: f64) -> &mut Self {
self.alpha = alpha.clamp(0.0, 1.0);
self
}
/// Computes the running cumulative sum from the change values.
///
/// For total bars, the cumulative sum equals the bar's value directly.
/// For non-total bars, each value is added to the running total.
///
/// Returns a vector of cumulative sums, one per category.
pub fn cumulative_sums(&self) -> Vec<f64> {
let n = self.values.len();
let mut cumsum = Vec::with_capacity(n);
let mut running = 0.0;
for i in 0..n {
if self.total_indices.contains(&i) {
running = self.values.data[i];
} else {
running += self.values.data[i];
}
cumsum.push(running);
}
cumsum
}
/// Returns the bar base and top positions for each category.
///
/// For non-total bars: base is the previous cumulative sum, top is the
/// current cumulative sum (base + change).
///
/// For total bars: base is 0, top is the cumulative sum value.
///
/// Returns a vector of `(base, top)` tuples.
pub fn bar_positions(&self) -> Vec<(f64, f64)> {
let cumsum = self.cumulative_sums();
let n = cumsum.len();
let mut positions = Vec::with_capacity(n);
for i in 0..n {
if self.total_indices.contains(&i) {
positions.push((0.0, cumsum[i]));
} else {
let base = if i == 0 { 0.0 } else { cumsum[i - 1] };
positions.push((base, cumsum[i]));
}
}
positions
}
/// Returns the connector line positions as pairs of y-values.
///
/// Each connector runs from the top of bar `i` to the start of bar `i+1`.
/// Returns a vector of `(from_y, to_x_index)` tuples where `from_y` is
/// the y-value the connector line runs at.
pub fn connector_positions(&self) -> Vec<(usize, f64)> {
if !self.connector_lines {
return Vec::new();
}
let cumsum = self.cumulative_sums();
let n = cumsum.len();
if n < 2 {
return Vec::new();
}
let mut connectors = Vec::with_capacity(n - 1);
for (i, &val) in cumsum.iter().enumerate().take(n - 1) {
connectors.push((i, val));
}
connectors
}
}