1use crate::visualization::ColorScheme;
2
3#[cfg(feature = "plotly")]
4use {
5 crate::visualization::GraphConfig,
6 crate::visualization::{Label2D, Series2D, Surface3D, TraceMode},
7 plotly::common::{Font, Line, Mode},
8 plotly::{Scatter, Surface},
9 rust_decimal::Decimal,
10};
11
12#[cfg(feature = "plotly")]
14#[must_use]
15pub fn make_scatter(series: &Series2D) -> Box<Scatter<Decimal, Decimal>> {
16 let mode = match series.mode {
17 TraceMode::Lines => Mode::Lines,
18 TraceMode::Markers => Mode::Markers,
19 TraceMode::LinesMarkers => Mode::LinesMarkers,
20 TraceMode::TextLabels => Mode::Text,
21 };
22
23 let mut trace = Scatter::new(series.x.clone(), series.y.clone())
25 .name(series.name.clone())
26 .mode(mode);
27
28 let mut line = Line::new();
30 if let Some(w) = series.line_width {
31 line = line.width(w);
32 }
33
34 if let Some(ref c) = series.line_color {
35 line = line.color(c.to_string());
36 }
37
38 trace = trace.line(line);
39
40 if matches!(series.mode, TraceMode::TextLabels) {
42 let text_vec = vec![series.name.clone(); series.x.len()];
44
45 trace = trace.text_array(text_vec);
47
48 trace = trace.mode(Mode::Text);
50
51 if let Some(ref color) = series.line_color {
53 let text_font = plotly::common::Font::new()
55 .color(color.to_string())
56 .size(16);
57 trace = trace.text_font(text_font);
58 }
59 } else if matches!(series.mode, TraceMode::Markers) && series.line_width == Some(0.0) {
60 let text_vec = vec![series.name.clone(); series.x.len()];
63
64 trace = trace.text_array(text_vec);
66
67 if let Some(ref color) = series.line_color {
69 let text_font = plotly::common::Font::new()
71 .color(color.to_string())
72 .size(16);
73 trace = trace.text_font(text_font);
74
75 let marker = plotly::common::Marker::new()
77 .size(10)
78 .color(color.to_string());
79 trace = trace.marker(marker);
80 }
81 }
82
83 trace
84}
85
86#[cfg(feature = "plotly")]
88#[must_use]
89pub fn pick_color(cfg: &GraphConfig, idx: usize) -> Option<String> {
90 get_color_from_scheme(&cfg.color_scheme, idx)
91}
92
93#[cfg(feature = "plotly")]
123#[must_use]
124pub fn make_surface(surf: &Surface3D) -> Box<Surface<Decimal, Decimal, Decimal>> {
125 if surf.x.is_empty() || surf.y.is_empty() || surf.z.is_empty() {
126 let z_matrix = vec![
127 vec![Decimal::ZERO, Decimal::ZERO],
128 vec![Decimal::ZERO, Decimal::ZERO],
129 ];
130 return Surface::new(z_matrix)
131 .x(vec![Decimal::ZERO, Decimal::ONE])
132 .y(vec![Decimal::ZERO, Decimal::ONE])
133 .name(&surf.name);
134 }
135
136 let mut x_set = std::collections::BTreeSet::new();
137 let mut y_set = std::collections::BTreeSet::new();
138
139 let points = || surf.x.iter().zip(&surf.y).zip(&surf.z);
141
142 for ((&x, &y), _) in points() {
143 x_set.insert(x);
144 y_set.insert(y);
145 }
146
147 let x_unique: Vec<_> = x_set.into_iter().collect();
148 let y_unique: Vec<_> = y_set.into_iter().collect();
149
150 let mut x_to_col = std::collections::HashMap::new();
151 let mut y_to_row = std::collections::HashMap::new();
152
153 for (col, &x) in x_unique.iter().enumerate() {
154 x_to_col.insert(x, col);
155 }
156
157 for (row, &y) in y_unique.iter().enumerate() {
158 y_to_row.insert(y, row);
159 }
160
161 let mut z_matrix = vec![vec![Decimal::ZERO; x_unique.len()]; y_unique.len()];
162
163 for ((x, y), &z) in points() {
164 if let (Some(&row), Some(&col)) = (y_to_row.get(y), x_to_col.get(x))
165 && let Some(cell) = z_matrix.get_mut(row).and_then(|cells| cells.get_mut(col))
166 {
167 *cell = z;
168 }
169 }
170
171 Surface::new(z_matrix)
172 .x(x_unique)
173 .y(y_unique)
174 .name(&surf.name)
175}
176
177#[cfg(feature = "plotly")]
179#[must_use]
180pub fn to_plotly_mode(mode: &TraceMode) -> Mode {
181 match mode {
182 TraceMode::Lines => Mode::Lines,
183 TraceMode::Markers => Mode::Markers,
184 TraceMode::LinesMarkers => Mode::LinesMarkers,
185 TraceMode::TextLabels => Mode::Text,
186 }
187}
188
189#[cfg(feature = "plotly")]
194#[allow(dead_code)]
195pub fn make_label_scatter(label: &Label2D) -> Box<Scatter<Decimal, Decimal>> {
196 let mut trace = Scatter::new(vec![label.point.x], vec![label.point.y])
198 .name(label.point.name.clone())
199 .mode(Mode::Text);
200
201 trace = trace.text_array(vec![label.label.clone()]);
203
204 if let Some(ref color) = label.point.color {
206 let text_font = Font::new().color(color.to_string()).size(16);
208 trace = trace.text_font(text_font);
209 }
210
211 trace
212}
213
214#[must_use]
216pub fn get_color_from_scheme(scheme: &ColorScheme, idx: usize) -> Option<String> {
217 match scheme {
218 ColorScheme::Default => None,
219 ColorScheme::Viridis => {
220 let colors = vec![
222 "#481567", "#33638D", "#20A387", "#95D840", "#440154", "#39568C", "#1F968B",
223 "#73D055", "#482677", "#2D708E", "#29AF7F", "#B8DE29", "#453781", "#287D8E",
224 "#3CBB75", "#DCE319", "#404788", "#238A8D", "#55C667", "#FDE725", "#46337E",
225 "#1E9C89", "#79D151", "#471365", "#375B8D", "#23A884", "#A1DB32", "#45287C",
226 "#307B8E", "#34B778", "#C6DE2F", "#432D7A", "#288A8D", "#42C675", "#E3E419",
227 "#3F4889", "#21968A", "#5DC864", "#F0E51B", "#461C74",
228 ];
229 let color = colors.get(idx.checked_rem(colors.len())?)?;
230 Some(color.to_string())
231 }
232 ColorScheme::Plasma => {
233 let colors = vec![
235 "#0D0887", "#A01C9C", "#F2844B", "#2A0593", "#B02A8F", "#F89540", "#41049D",
236 "#BF3983", "#FCA636", "#5601A4", "#CB4678", "#FDB92F", "#6A00A8", "#D6556D",
237 "#FECE2F", "#7E03A8", "#E16462", "#FCFD35", "#8F0DA4", "#EA7457", "#330597",
238 "#B93779", "#F68F46", "#4C02A1", "#CC3F76", "#FBA238", "#6501AB", "#DC5267",
239 "#FDC229", "#7B04A7", "#E36159", "#FED330", "#8D0BA2", "#E7704F", "#FEE54F",
240 "#9E189B", "#EC7F45", "#FEF06F", "#1C0377", "#AC2294",
241 ];
242 let color = colors.get(idx.checked_rem(colors.len())?)?;
243 Some(color.to_string())
244 }
245 ColorScheme::Custom(list) => {
246 list.get(idx.checked_rem(list.len())?).cloned()
249 }
250 ColorScheme::White => Some("#FFFFFF".to_string()),
251 ColorScheme::HighContrast => {
252 let colors = vec![
253 "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#1F77B4",
254 "#FF7F0E", "#2CA02C", "#D62728", "#9467BD", "#8C564B", "#E377C2", "#7F7F7F",
255 "#BCBD22", "#17BECF", "#800000", "#008000", "#000080", "#808000", "#800080",
256 "#008080", "#FF8C00", "#90EE90", "#ADD8E6", "#FFA07A", "#DA70D6", "#FFD700",
257 "#FF1493", "#4B0082", "#006400", "#483D8B", "#CD853F", "#8B4513", "#4682B4",
258 "#9ACD32", "#B22222", "#A52A2A", "#6A5ACD", "#778899", "#FF6347", "#7CFC00",
259 "#87CEFA", "#FFA500", "#9932CC", "#008B8B",
260 ];
261 let color = colors.get(idx.checked_rem(colors.len())?)?;
262 Some(color.to_string())
263 }
264 }
265}