1use crate::error::{ChartError, ChartResult};
4use crate::legend::{
5 position::{LegendAlignment, LegendMargins, LegendPosition},
6 style::LegendStyle,
7 traits::Legend,
8 types::{
9 CompactLegend, CompactLegendEntry, CustomLegend, CustomLegendEntry, LegendEntryType,
10 LegendOrientation, StandardLegend, StandardLegendEntry,
11 },
12};
13use embedded_graphics::prelude::*;
14
15pub trait LegendBuilder<C: PixelColor> {
17 type Legend: Legend<C>;
19 type Error;
21
22 fn build(self) -> Result<Self::Legend, Self::Error>;
24}
25
26#[derive(Debug)]
28pub struct StandardLegendBuilder<C: PixelColor> {
29 position: LegendPosition,
30 orientation: LegendOrientation,
31 style: LegendStyle<C>,
32 alignment: LegendAlignment,
33 margins: LegendMargins,
34 entries: heapless::Vec<StandardLegendEntry<C>, 16>,
35}
36
37#[derive(Debug)]
39pub struct CompactLegendBuilder<C: PixelColor> {
40 position: LegendPosition,
41 orientation: LegendOrientation,
42 style: LegendStyle<C>,
43 #[allow(dead_code)]
44 alignment: LegendAlignment,
45 #[allow(dead_code)]
46 margins: LegendMargins,
47 entries: heapless::Vec<CompactLegendEntry<C>, 8>,
48}
49
50#[derive(Debug)]
52pub struct CustomLegendBuilder<C: PixelColor> {
53 position: LegendPosition,
54 orientation: LegendOrientation,
55 style: LegendStyle<C>,
56 #[allow(dead_code)]
57 alignment: LegendAlignment,
58 #[allow(dead_code)]
59 margins: LegendMargins,
60 entries: heapless::Vec<CustomLegendEntry<C>, 12>,
61 layout_params: crate::legend::types::CustomLayoutParams,
62}
63
64impl<C: PixelColor> StandardLegendBuilder<C>
65where
66 C: From<embedded_graphics::pixelcolor::Rgb565>,
67{
68 pub fn new() -> Self {
70 Self {
71 position: LegendPosition::Right,
72 orientation: LegendOrientation::Vertical,
73 style: LegendStyle::new(),
74 alignment: LegendAlignment::Start,
75 margins: LegendMargins::default(),
76 entries: heapless::Vec::new(),
77 }
78 }
79
80 pub fn position(mut self, position: LegendPosition) -> Self {
82 self.position = position;
83 self
84 }
85
86 pub fn orientation(mut self, orientation: LegendOrientation) -> Self {
88 self.orientation = orientation;
89 self
90 }
91
92 pub fn style(mut self, style: LegendStyle<C>) -> Self {
94 self.style = style;
95 self
96 }
97
98 pub fn minimal_style(mut self) -> Self {
100 self.style = LegendStyle::minimal();
101 self
102 }
103
104 pub fn professional_style(mut self) -> Self {
106 self.style = LegendStyle::professional();
107 self
108 }
109
110 pub fn compact_style(mut self) -> Self {
112 self.style = LegendStyle::compact();
113 self
114 }
115
116 pub fn alignment(mut self, alignment: LegendAlignment) -> Self {
118 self.alignment = alignment;
119 self
120 }
121
122 pub fn margins(mut self, margins: LegendMargins) -> Self {
124 self.margins = margins;
125 self
126 }
127
128 pub fn add_line_entry(mut self, label: &str, color: C) -> ChartResult<Self> {
130 let entry_type = LegendEntryType::Line {
131 color,
132 width: 2,
133 pattern: crate::style::LinePattern::Solid,
134 marker: None,
135 };
136 let entry = StandardLegendEntry::new(label, entry_type)?;
137 self.entries
138 .push(entry)
139 .map_err(|_| ChartError::ConfigurationError)?;
140 Ok(self)
141 }
142
143 pub fn add_line_entry_with_marker(
145 mut self,
146 label: &str,
147 color: C,
148 marker: crate::legend::types::MarkerStyle<C>,
149 ) -> ChartResult<Self> {
150 let entry_type = LegendEntryType::Line {
151 color,
152 width: 2,
153 pattern: crate::style::LinePattern::Solid,
154 marker: Some(marker),
155 };
156 let entry = StandardLegendEntry::new(label, entry_type)?;
157 self.entries
158 .push(entry)
159 .map_err(|_| ChartError::ConfigurationError)?;
160 Ok(self)
161 }
162
163 pub fn add_bar_entry(mut self, label: &str, color: C) -> ChartResult<Self> {
165 let entry_type = LegendEntryType::Bar {
166 color,
167 border_color: None,
168 border_width: 0,
169 };
170 let entry = StandardLegendEntry::new(label, entry_type)?;
171 self.entries
172 .push(entry)
173 .map_err(|_| ChartError::ConfigurationError)?;
174 Ok(self)
175 }
176
177 pub fn add_bar_entry_with_border(
179 mut self,
180 label: &str,
181 color: C,
182 border_color: C,
183 border_width: u32,
184 ) -> ChartResult<Self> {
185 let entry_type = LegendEntryType::Bar {
186 color,
187 border_color: Some(border_color),
188 border_width,
189 };
190 let entry = StandardLegendEntry::new(label, entry_type)?;
191 self.entries
192 .push(entry)
193 .map_err(|_| ChartError::ConfigurationError)?;
194 Ok(self)
195 }
196
197 pub fn add_pie_entry(mut self, label: &str, color: C) -> ChartResult<Self> {
199 let entry_type = LegendEntryType::Pie {
200 color,
201 border_color: None,
202 border_width: 0,
203 };
204 let entry = StandardLegendEntry::new(label, entry_type)?;
205 self.entries
206 .push(entry)
207 .map_err(|_| ChartError::ConfigurationError)?;
208 Ok(self)
209 }
210
211 pub fn add_custom_entry(
213 mut self,
214 label: &str,
215 color: C,
216 shape: crate::legend::types::SymbolShape,
217 size: u32,
218 ) -> ChartResult<Self> {
219 let entry_type = LegendEntryType::Custom { color, shape, size };
220 let entry = StandardLegendEntry::new(label, entry_type)?;
221 self.entries
222 .push(entry)
223 .map_err(|_| ChartError::ConfigurationError)?;
224 Ok(self)
225 }
226
227 pub fn add_entry(mut self, label: &str, entry_type: LegendEntryType<C>) -> ChartResult<Self> {
229 let entry = StandardLegendEntry::new(label, entry_type)?;
230 self.entries
231 .push(entry)
232 .map_err(|_| ChartError::ConfigurationError)?;
233 Ok(self)
234 }
235
236 pub fn clear_entries(mut self) -> Self {
238 self.entries.clear();
239 self
240 }
241}
242
243impl<C: PixelColor> LegendBuilder<C> for StandardLegendBuilder<C>
244where
245 C: From<embedded_graphics::pixelcolor::Rgb565>,
246{
247 type Legend = StandardLegend<C>;
248 type Error = ChartError;
249
250 fn build(self) -> Result<Self::Legend, Self::Error> {
251 let mut legend = StandardLegend::new(self.position);
252 legend.set_orientation(self.orientation);
253 legend.set_style(self.style);
254
255 for entry in self.entries {
257 legend.add_entry(entry)?;
258 }
259
260 Ok(legend)
261 }
262}
263
264impl<C: PixelColor> Default for StandardLegendBuilder<C>
265where
266 C: From<embedded_graphics::pixelcolor::Rgb565>,
267{
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273impl<C: PixelColor> CompactLegendBuilder<C>
274where
275 C: From<embedded_graphics::pixelcolor::Rgb565>,
276{
277 pub fn new() -> Self {
279 Self {
280 position: LegendPosition::Right,
281 orientation: LegendOrientation::Vertical,
282 style: LegendStyle::compact(),
283 alignment: LegendAlignment::Start,
284 margins: LegendMargins::all(4),
285 entries: heapless::Vec::new(),
286 }
287 }
288
289 pub fn position(mut self, position: LegendPosition) -> Self {
291 self.position = position;
292 self
293 }
294
295 pub fn orientation(mut self, orientation: LegendOrientation) -> Self {
297 self.orientation = orientation;
298 self
299 }
300
301 pub fn add_simple_entry(mut self, label: &str, color: C) -> ChartResult<Self> {
303 let entry_type = LegendEntryType::Custom {
304 color,
305 shape: crate::legend::types::SymbolShape::Circle,
306 size: 8,
307 };
308 let entry = CompactLegendEntry::new(label, entry_type)?;
309 self.entries
310 .push(entry)
311 .map_err(|_| ChartError::ConfigurationError)?;
312 Ok(self)
313 }
314}
315
316impl<C: PixelColor> LegendBuilder<C> for CompactLegendBuilder<C>
317where
318 C: From<embedded_graphics::pixelcolor::Rgb565>,
319{
320 type Legend = CompactLegend<C>;
321 type Error = ChartError;
322
323 fn build(self) -> Result<Self::Legend, Self::Error> {
324 let mut legend = CompactLegend::new(self.position);
325 legend.set_orientation(self.orientation);
326 legend.set_style(self.style);
327
328 for entry in self.entries {
330 legend.add_entry(entry)?;
331 }
332
333 Ok(legend)
334 }
335}
336
337impl<C: PixelColor> Default for CompactLegendBuilder<C>
338where
339 C: From<embedded_graphics::pixelcolor::Rgb565>,
340{
341 fn default() -> Self {
342 Self::new()
343 }
344}
345
346impl<C: PixelColor> CustomLegendBuilder<C>
347where
348 C: From<embedded_graphics::pixelcolor::Rgb565>,
349{
350 pub fn new() -> Self {
352 Self {
353 position: LegendPosition::Right,
354 orientation: LegendOrientation::Vertical,
355 style: LegendStyle::new(),
356 alignment: LegendAlignment::Start,
357 margins: LegendMargins::default(),
358 entries: heapless::Vec::new(),
359 layout_params: crate::legend::types::CustomLayoutParams::default(),
360 }
361 }
362
363 pub fn position(mut self, position: LegendPosition) -> Self {
365 self.position = position;
366 self
367 }
368
369 pub fn orientation(mut self, orientation: LegendOrientation) -> Self {
371 self.orientation = orientation;
372 self
373 }
374
375 pub fn layout_params(mut self, params: crate::legend::types::CustomLayoutParams) -> Self {
377 self.layout_params = params;
378 self
379 }
380
381 pub fn entry_spacing(mut self, spacing: u32) -> Self {
383 self.layout_params.entry_spacing = spacing;
384 self
385 }
386
387 pub fn symbol_size(mut self, size: u32) -> Self {
389 self.layout_params.symbol_size = size;
390 self
391 }
392
393 pub fn auto_layout(mut self, enabled: bool) -> Self {
395 self.layout_params.auto_layout = enabled;
396 self
397 }
398}
399
400impl<C: PixelColor> LegendBuilder<C> for CustomLegendBuilder<C>
401where
402 C: From<embedded_graphics::pixelcolor::Rgb565>,
403{
404 type Legend = CustomLegend<C>;
405 type Error = ChartError;
406
407 fn build(self) -> Result<Self::Legend, Self::Error> {
408 let mut legend = CustomLegend::new(self.position);
409 legend.set_orientation(self.orientation);
410 legend.set_style(self.style);
411 legend.set_layout_params(self.layout_params);
412
413 for entry in self.entries {
415 legend.add_entry(entry)?;
416 }
417
418 Ok(legend)
419 }
420}
421
422impl<C: PixelColor> Default for CustomLegendBuilder<C>
423where
424 C: From<embedded_graphics::pixelcolor::Rgb565>,
425{
426 fn default() -> Self {
427 Self::new()
428 }
429}
430
431pub mod presets {
434 use super::*;
435 use embedded_graphics::pixelcolor::Rgb565;
436
437 pub fn right_legend<C: PixelColor + From<Rgb565>>() -> StandardLegendBuilder<C> {
439 StandardLegendBuilder::new()
440 .position(LegendPosition::Right)
441 .orientation(LegendOrientation::Vertical)
442 }
443
444 pub fn bottom_legend<C: PixelColor + From<Rgb565>>() -> StandardLegendBuilder<C> {
446 StandardLegendBuilder::new()
447 .position(LegendPosition::Bottom)
448 .orientation(LegendOrientation::Horizontal)
449 }
450
451 pub fn minimal_legend<C: PixelColor + From<Rgb565>>() -> CompactLegendBuilder<C> {
453 CompactLegendBuilder::new()
454 .position(LegendPosition::TopRight)
455 .orientation(LegendOrientation::Vertical)
456 }
457
458 pub fn professional_legend<C: PixelColor + From<Rgb565>>() -> StandardLegendBuilder<C> {
460 StandardLegendBuilder::new()
461 .position(LegendPosition::Right)
462 .professional_style()
463 .margins(LegendMargins::all(12))
464 }
465
466 pub fn floating_legend<C: PixelColor + From<Rgb565>>(
468 position: Point,
469 ) -> StandardLegendBuilder<C> {
470 StandardLegendBuilder::new()
471 .position(LegendPosition::Floating(position))
472 .orientation(LegendOrientation::Vertical)
473 }
474}
475
476#[cfg(test)]
479mod tests {
480 use super::*;
481 use embedded_graphics::pixelcolor::Rgb565;
482
483 #[test]
484 fn test_standard_legend_builder() {
485 let legend = StandardLegendBuilder::new()
486 .position(LegendPosition::Bottom)
487 .orientation(LegendOrientation::Horizontal)
488 .add_line_entry("Series 1", Rgb565::RED)
489 .unwrap()
490 .add_bar_entry("Series 2", Rgb565::BLUE)
491 .unwrap()
492 .build()
493 .unwrap();
494
495 assert_eq!(legend.position(), LegendPosition::Bottom);
496 assert_eq!(legend.orientation(), LegendOrientation::Horizontal);
497 assert_eq!(legend.entries().len(), 2);
498 }
499
500 #[test]
501 fn test_preset_legends() {
502 let legend = presets::right_legend::<Rgb565>()
503 .add_line_entry("Test", Rgb565::GREEN)
504 .unwrap()
505 .build()
506 .unwrap();
507
508 assert_eq!(legend.position(), LegendPosition::Right);
509 assert_eq!(legend.orientation(), LegendOrientation::Vertical);
510 }
511}