embedded_charts/legend/
types.rs

1//! Legend type implementations.
2
3use crate::error::{ChartError, ChartResult};
4use crate::legend::position::LegendPosition;
5use crate::legend::style::{LegendStyle, SymbolStyle};
6use crate::legend::traits::{Legend, LegendEntry};
7use embedded_graphics::{prelude::*, primitives::Rectangle};
8
9#[cfg(feature = "std")]
10use std::vec::Vec;
11
12#[cfg(all(feature = "no_std", not(feature = "std")))]
13extern crate alloc;
14
15#[cfg(all(feature = "no_std", not(feature = "std")))]
16use alloc::vec::Vec;
17
18/// Legend orientation options
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum LegendOrientation {
21    /// Vertical layout (entries stacked vertically)
22    Vertical,
23    /// Horizontal layout (entries arranged horizontally)
24    Horizontal,
25}
26
27/// Types of legend entries
28#[derive(Debug, Clone)]
29pub enum LegendEntryType<C: PixelColor> {
30    /// Line entry for line charts
31    Line {
32        /// Line color
33        color: C,
34        /// Line width
35        width: u32,
36        /// Line pattern (solid, dashed, etc.)
37        pattern: crate::style::LinePattern,
38        /// Optional marker
39        marker: Option<MarkerStyle<C>>,
40    },
41    /// Bar entry for bar charts
42    Bar {
43        /// Fill color
44        color: C,
45        /// Optional border color
46        border_color: Option<C>,
47        /// Border width
48        border_width: u32,
49    },
50    /// Pie entry for pie charts
51    Pie {
52        /// Fill color
53        color: C,
54        /// Optional border color
55        border_color: Option<C>,
56        /// Border width
57        border_width: u32,
58    },
59    /// Custom symbol entry
60    Custom {
61        /// Symbol color
62        color: C,
63        /// Symbol shape
64        shape: SymbolShape,
65        /// Symbol size
66        size: u32,
67    },
68}
69
70/// Marker styles for line entries
71#[derive(Debug, Clone)]
72pub struct MarkerStyle<C: PixelColor> {
73    /// Marker shape
74    pub shape: MarkerShape,
75    /// Marker color
76    pub color: C,
77    /// Marker size
78    pub size: u32,
79    /// Whether to fill the marker
80    pub filled: bool,
81}
82
83/// Available marker shapes
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum MarkerShape {
86    /// Circle marker
87    Circle,
88    /// Square marker
89    Square,
90    /// Triangle marker
91    Triangle,
92    /// Diamond marker
93    Diamond,
94    /// Cross marker
95    Cross,
96    /// Plus marker
97    Plus,
98}
99
100/// Available symbol shapes for custom entries
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum SymbolShape {
103    /// Circle symbol
104    Circle,
105    /// Square symbol
106    Square,
107    /// Triangle symbol
108    Triangle,
109    /// Diamond symbol
110    Diamond,
111    /// Star symbol
112    Star,
113    /// Cross symbol
114    Cross,
115}
116
117/// Standard legend implementation
118#[derive(Debug, Clone)]
119pub struct StandardLegend<C: PixelColor> {
120    /// Legend entries
121    entries: heapless::Vec<StandardLegendEntry<C>, 16>,
122    /// Legend position
123    position: LegendPosition,
124    /// Legend orientation
125    orientation: LegendOrientation,
126    /// Legend style
127    style: LegendStyle<C>,
128}
129
130/// Standard legend entry
131#[derive(Debug, Clone)]
132pub struct StandardLegendEntry<C: PixelColor> {
133    /// Entry label
134    label: heapless::String<64>,
135    /// Entry type
136    entry_type: LegendEntryType<C>,
137    /// Visibility flag
138    visible: bool,
139}
140
141/// Compact legend for space-constrained environments
142#[derive(Debug, Clone)]
143pub struct CompactLegend<C: PixelColor> {
144    /// Legend entries (limited capacity)
145    entries: heapless::Vec<CompactLegendEntry<C>, 8>,
146    /// Legend position
147    position: LegendPosition,
148    /// Legend orientation
149    orientation: LegendOrientation,
150    /// Legend style
151    style: LegendStyle<C>,
152}
153
154impl<C: PixelColor> CompactLegend<C>
155where
156    C: From<embedded_graphics::pixelcolor::Rgb565>,
157{
158    /// Create a new compact legend
159    pub fn new(position: LegendPosition) -> Self {
160        Self {
161            entries: heapless::Vec::new(),
162            position,
163            orientation: LegendOrientation::Vertical,
164            style: LegendStyle::compact(),
165        }
166    }
167
168    /// Set the legend orientation
169    pub fn set_orientation(&mut self, orientation: LegendOrientation) {
170        self.orientation = orientation;
171    }
172
173    /// Set the legend style
174    pub fn set_style(&mut self, style: LegendStyle<C>) {
175        self.style = style;
176    }
177
178    /// Add an entry to the legend
179    pub fn add_entry(&mut self, entry: CompactLegendEntry<C>) -> ChartResult<()> {
180        self.entries
181            .push(entry)
182            .map_err(|_| ChartError::ConfigurationError)?;
183        Ok(())
184    }
185}
186
187impl<C: PixelColor> crate::legend::traits::Legend<C> for CompactLegend<C>
188where
189    C: From<embedded_graphics::pixelcolor::Rgb565>,
190{
191    type Entry = CompactLegendEntry<C>;
192
193    fn position(&self) -> LegendPosition {
194        self.position
195    }
196
197    fn orientation(&self) -> LegendOrientation {
198        self.orientation
199    }
200
201    fn entries(&self) -> &[Self::Entry] {
202        &self.entries
203    }
204
205    fn entries_mut(&mut self) -> &mut [Self::Entry] {
206        &mut self.entries
207    }
208
209    fn add_entry(&mut self, entry: Self::Entry) -> ChartResult<()> {
210        self.entries
211            .push(entry)
212            .map_err(|_| ChartError::ConfigurationError)?;
213        Ok(())
214    }
215
216    fn remove_entry(&mut self, index: usize) -> ChartResult<()> {
217        if index < self.entries.len() {
218            self.entries.swap_remove(index);
219            Ok(())
220        } else {
221            Err(ChartError::InvalidConfiguration)
222        }
223    }
224
225    fn clear_entries(&mut self) {
226        self.entries.clear();
227    }
228
229    fn set_position(&mut self, position: LegendPosition) {
230        self.position = position;
231    }
232
233    fn set_orientation(&mut self, orientation: LegendOrientation) {
234        self.orientation = orientation;
235    }
236
237    fn calculate_size(&self) -> embedded_graphics::prelude::Size {
238        // Simple size calculation for compact legend
239        let entry_count = self.entries.len() as u32;
240        match self.orientation {
241            LegendOrientation::Vertical => {
242                embedded_graphics::prelude::Size::new(80, entry_count * 16 + 8)
243            }
244            LegendOrientation::Horizontal => {
245                embedded_graphics::prelude::Size::new(entry_count * 60 + 8, 20)
246            }
247        }
248    }
249}
250
251/// Compact legend entry with shorter labels
252#[derive(Debug, Clone)]
253pub struct CompactLegendEntry<C: PixelColor> {
254    /// Entry label (shorter for compact display)
255    pub label: heapless::String<16>,
256    /// Entry type
257    pub entry_type: LegendEntryType<C>,
258    /// Visibility flag
259    pub visible: bool,
260}
261
262impl<C: PixelColor> CompactLegendEntry<C> {
263    /// Create a new compact legend entry
264    pub fn new(label: &str, entry_type: LegendEntryType<C>) -> ChartResult<Self> {
265        let mut label_string = heapless::String::new();
266        label_string
267            .push_str(label)
268            .map_err(|_| ChartError::ConfigurationError)?;
269        Ok(Self {
270            label: label_string,
271            entry_type,
272            visible: true,
273        })
274    }
275}
276
277/// Custom legend for specialized layouts
278#[derive(Debug, Clone)]
279pub struct CustomLegend<C: PixelColor> {
280    /// Legend entries
281    entries: heapless::Vec<CustomLegendEntry<C>, 12>,
282    /// Legend position
283    position: LegendPosition,
284    /// Legend orientation
285    orientation: LegendOrientation,
286    /// Legend style
287    style: LegendStyle<C>,
288    /// Custom layout parameters
289    layout_params: CustomLayoutParams,
290}
291
292impl<C: PixelColor> CustomLegend<C>
293where
294    C: From<embedded_graphics::pixelcolor::Rgb565>,
295{
296    /// Create a new custom legend
297    pub fn new(position: LegendPosition) -> Self {
298        Self {
299            entries: heapless::Vec::new(),
300            position,
301            orientation: LegendOrientation::Vertical,
302            style: LegendStyle::new(),
303            layout_params: CustomLayoutParams::default(),
304        }
305    }
306
307    /// Set the legend orientation
308    pub fn set_orientation(&mut self, orientation: LegendOrientation) {
309        self.orientation = orientation;
310    }
311
312    /// Set the legend style
313    pub fn set_style(&mut self, style: LegendStyle<C>) {
314        self.style = style;
315    }
316
317    /// Set custom layout parameters
318    pub fn set_layout_params(&mut self, params: CustomLayoutParams) {
319        self.layout_params = params;
320    }
321
322    /// Add an entry to the legend
323    pub fn add_entry(&mut self, entry: CustomLegendEntry<C>) -> ChartResult<()> {
324        self.entries
325            .push(entry)
326            .map_err(|_| ChartError::ConfigurationError)?;
327        Ok(())
328    }
329}
330
331impl<C: PixelColor> crate::legend::traits::Legend<C> for CustomLegend<C>
332where
333    C: From<embedded_graphics::pixelcolor::Rgb565>,
334{
335    type Entry = CustomLegendEntry<C>;
336
337    fn position(&self) -> LegendPosition {
338        self.position
339    }
340
341    fn orientation(&self) -> LegendOrientation {
342        self.orientation
343    }
344
345    fn entries(&self) -> &[Self::Entry] {
346        &self.entries
347    }
348
349    fn entries_mut(&mut self) -> &mut [Self::Entry] {
350        &mut self.entries
351    }
352
353    fn add_entry(&mut self, entry: Self::Entry) -> ChartResult<()> {
354        self.entries
355            .push(entry)
356            .map_err(|_| ChartError::ConfigurationError)?;
357        Ok(())
358    }
359
360    fn remove_entry(&mut self, index: usize) -> ChartResult<()> {
361        if index < self.entries.len() {
362            self.entries.swap_remove(index);
363            Ok(())
364        } else {
365            Err(ChartError::InvalidConfiguration)
366        }
367    }
368
369    fn clear_entries(&mut self) {
370        self.entries.clear();
371    }
372
373    fn set_position(&mut self, position: LegendPosition) {
374        self.position = position;
375    }
376
377    fn set_orientation(&mut self, orientation: LegendOrientation) {
378        self.orientation = orientation;
379    }
380
381    fn calculate_size(&self) -> embedded_graphics::prelude::Size {
382        // Size calculation based on layout parameters
383        let entry_count = self.entries.len() as u32;
384        let symbol_size = self.layout_params.symbol_size;
385        let entry_spacing = self.layout_params.entry_spacing;
386
387        match self.orientation {
388            LegendOrientation::Vertical => {
389                let width = symbol_size + 100; // Symbol + text space
390                let height = entry_count * (symbol_size + entry_spacing) + 16;
391                embedded_graphics::prelude::Size::new(width, height)
392            }
393            LegendOrientation::Horizontal => {
394                let width = entry_count * (symbol_size + 80 + entry_spacing) + 16;
395                let height = symbol_size + 16;
396                embedded_graphics::prelude::Size::new(width, height)
397            }
398        }
399    }
400}
401
402/// Custom legend entry with additional metadata
403#[derive(Debug, Clone)]
404pub struct CustomLegendEntry<C: PixelColor> {
405    /// Entry label
406    label: heapless::String<32>,
407    /// Entry type
408    entry_type: LegendEntryType<C>,
409    /// Visibility flag
410    visible: bool,
411    /// Custom positioning offset
412    #[allow(dead_code)]
413    offset: Point,
414    /// Custom size override
415    size_override: Option<Size>,
416}
417
418impl<C: PixelColor> CustomLegendEntry<C> {
419    /// Create a new custom legend entry
420    pub fn new(label: &str, entry_type: LegendEntryType<C>) -> ChartResult<Self> {
421        let mut label_string = heapless::String::new();
422        label_string
423            .push_str(label)
424            .map_err(|_| ChartError::ConfigurationError)?;
425        Ok(Self {
426            label: label_string,
427            entry_type,
428            visible: true,
429            offset: embedded_graphics::prelude::Point::zero(),
430            size_override: None,
431        })
432    }
433}
434
435/// Custom layout parameters
436#[derive(Debug, Clone)]
437pub struct CustomLayoutParams {
438    /// Custom entry spacing
439    pub entry_spacing: u32,
440    /// Custom symbol size
441    pub symbol_size: u32,
442    /// Custom text offset
443    pub text_offset: Point,
444    /// Whether to use automatic layout
445    pub auto_layout: bool,
446}
447
448// Implementation for StandardLegend
449impl<C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565>> StandardLegend<C> {
450    /// Create a new standard legend
451    pub fn new(position: LegendPosition) -> Self {
452        Self {
453            entries: heapless::Vec::new(),
454            position,
455            orientation: LegendOrientation::Vertical,
456            style: LegendStyle::default(),
457        }
458    }
459
460    /// Set the legend style
461    pub fn set_style(&mut self, style: LegendStyle<C>) {
462        self.style = style;
463    }
464
465    /// Get the legend style
466    pub fn style(&self) -> &LegendStyle<C> {
467        &self.style
468    }
469}
470
471impl<C: PixelColor> Legend<C> for StandardLegend<C> {
472    type Entry = StandardLegendEntry<C>;
473
474    fn entries(&self) -> &[Self::Entry] {
475        &self.entries
476    }
477
478    fn entries_mut(&mut self) -> &mut [Self::Entry] {
479        &mut self.entries
480    }
481
482    fn add_entry(&mut self, entry: Self::Entry) -> ChartResult<()> {
483        self.entries
484            .push(entry)
485            .map_err(|_| crate::error::ChartError::ConfigurationError)
486    }
487
488    fn remove_entry(&mut self, index: usize) -> ChartResult<()> {
489        if index < self.entries.len() {
490            self.entries.remove(index);
491            Ok(())
492        } else {
493            Err(crate::error::ChartError::ConfigurationError)
494        }
495    }
496
497    fn clear_entries(&mut self) {
498        self.entries.clear();
499    }
500
501    fn position(&self) -> LegendPosition {
502        self.position
503    }
504
505    fn set_position(&mut self, position: LegendPosition) {
506        self.position = position;
507    }
508
509    fn orientation(&self) -> LegendOrientation {
510        self.orientation
511    }
512
513    fn set_orientation(&mut self, orientation: LegendOrientation) {
514        self.orientation = orientation;
515    }
516
517    fn calculate_size(&self) -> Size {
518        if self.entries.is_empty() {
519            return Size::zero();
520        }
521
522        let visible_entries: Vec<_> = self.entries.iter().filter(|e| e.visible).collect();
523        if visible_entries.is_empty() {
524            return Size::zero();
525        }
526
527        match self.orientation {
528            LegendOrientation::Vertical => {
529                let max_width = visible_entries
530                    .iter()
531                    .map(|e| e.calculate_size(&self.style).width)
532                    .max()
533                    .unwrap_or(0);
534                let total_height = visible_entries
535                    .iter()
536                    .map(|e| e.calculate_size(&self.style).height)
537                    .sum::<u32>()
538                    + (visible_entries.len().saturating_sub(1) as u32
539                        * self.style.spacing.entry_spacing);
540                Size::new(max_width, total_height)
541            }
542            LegendOrientation::Horizontal => {
543                let total_width = visible_entries
544                    .iter()
545                    .map(|e| e.calculate_size(&self.style).width)
546                    .sum::<u32>()
547                    + (visible_entries.len().saturating_sub(1) as u32
548                        * self.style.spacing.entry_spacing);
549                let max_height = visible_entries
550                    .iter()
551                    .map(|e| e.calculate_size(&self.style).height)
552                    .max()
553                    .unwrap_or(0);
554                Size::new(total_width, max_height)
555            }
556        }
557    }
558}
559
560// Implementation for StandardLegendEntry
561impl<C: PixelColor> StandardLegendEntry<C> {
562    /// Create a new standard legend entry
563    pub fn new(label: &str, entry_type: LegendEntryType<C>) -> ChartResult<Self> {
564        let label_string = heapless::String::try_from(label)
565            .map_err(|_| crate::error::ChartError::ConfigurationError)?;
566
567        Ok(Self {
568            label: label_string,
569            entry_type,
570            visible: true,
571        })
572    }
573}
574
575impl<C: PixelColor> LegendEntry<C> for StandardLegendEntry<C> {
576    fn label(&self) -> &str {
577        &self.label
578    }
579
580    fn set_label(&mut self, label: &str) -> ChartResult<()> {
581        self.label = heapless::String::try_from(label)
582            .map_err(|_| crate::error::ChartError::ConfigurationError)?;
583        Ok(())
584    }
585
586    fn entry_type(&self) -> &LegendEntryType<C> {
587        &self.entry_type
588    }
589
590    fn set_entry_type(&mut self, entry_type: LegendEntryType<C>) {
591        self.entry_type = entry_type;
592    }
593
594    fn is_visible(&self) -> bool {
595        self.visible
596    }
597
598    fn set_visible(&mut self, visible: bool) {
599        self.visible = visible;
600    }
601
602    fn calculate_size(&self, style: &LegendStyle<C>) -> Size {
603        let text_width = self.label.len() as u32 * style.text.char_width;
604        let total_width = style.spacing.symbol_width + style.spacing.symbol_text_gap + text_width;
605        Size::new(total_width, style.text.line_height)
606    }
607
608    fn render_symbol<D>(
609        &self,
610        bounds: Rectangle,
611        _style: &SymbolStyle<C>,
612        target: &mut D,
613    ) -> ChartResult<()>
614    where
615        D: DrawTarget<Color = C>,
616    {
617        use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle as EgRectangle};
618
619        match &self.entry_type {
620            LegendEntryType::Line { color, .. } => {
621                // Draw a small line segment
622                let line_y = bounds.top_left.y + bounds.size.height as i32 / 2;
623                let line_start = Point::new(bounds.top_left.x + 2, line_y);
624                let line_end = Point::new(bounds.top_left.x + bounds.size.width as i32 - 2, line_y);
625
626                use embedded_graphics::primitives::Line;
627                Line::new(line_start, line_end)
628                    .into_styled(PrimitiveStyle::with_stroke(*color, 1))
629                    .draw(target)
630                    .map_err(|_| crate::error::ChartError::RenderingError)?;
631            }
632            LegendEntryType::Bar { color, .. } | LegendEntryType::Pie { color, .. } => {
633                // Draw a small rectangle
634                let rect_size = Size::new(bounds.size.width.min(16), bounds.size.height.min(12));
635                let rect_pos = Point::new(
636                    bounds.top_left.x + (bounds.size.width as i32 - rect_size.width as i32) / 2,
637                    bounds.top_left.y + (bounds.size.height as i32 - rect_size.height as i32) / 2,
638                );
639
640                EgRectangle::new(rect_pos, rect_size)
641                    .into_styled(PrimitiveStyle::with_fill(*color))
642                    .draw(target)
643                    .map_err(|_| crate::error::ChartError::RenderingError)?;
644            }
645            LegendEntryType::Custom { color, shape, size } => {
646                let symbol_size = (*size).min(bounds.size.width).min(bounds.size.height);
647                let center = Point::new(
648                    bounds.top_left.x + bounds.size.width as i32 / 2,
649                    bounds.top_left.y + bounds.size.height as i32 / 2,
650                );
651
652                match shape {
653                    SymbolShape::Circle => {
654                        Circle::with_center(center, symbol_size)
655                            .into_styled(PrimitiveStyle::with_fill(*color))
656                            .draw(target)
657                            .map_err(|_| crate::error::ChartError::RenderingError)?;
658                    }
659                    SymbolShape::Square => {
660                        let rect_size = Size::new(symbol_size, symbol_size);
661                        let rect_pos = Point::new(
662                            center.x - symbol_size as i32 / 2,
663                            center.y - symbol_size as i32 / 2,
664                        );
665                        EgRectangle::new(rect_pos, rect_size)
666                            .into_styled(PrimitiveStyle::with_fill(*color))
667                            .draw(target)
668                            .map_err(|_| crate::error::ChartError::RenderingError)?;
669                    }
670                    _ => {
671                        // For other shapes, draw a circle as fallback
672                        Circle::with_center(center, symbol_size)
673                            .into_styled(PrimitiveStyle::with_fill(*color))
674                            .draw(target)
675                            .map_err(|_| crate::error::ChartError::RenderingError)?;
676                    }
677                }
678            }
679        }
680
681        Ok(())
682    }
683}
684
685impl<C: PixelColor> LegendEntry<C> for CompactLegendEntry<C> {
686    fn label(&self) -> &str {
687        &self.label
688    }
689
690    fn set_label(&mut self, label: &str) -> ChartResult<()> {
691        self.label = heapless::String::try_from(label)
692            .map_err(|_| crate::error::ChartError::ConfigurationError)?;
693        Ok(())
694    }
695
696    fn entry_type(&self) -> &LegendEntryType<C> {
697        &self.entry_type
698    }
699
700    fn set_entry_type(&mut self, entry_type: LegendEntryType<C>) {
701        self.entry_type = entry_type;
702    }
703
704    fn is_visible(&self) -> bool {
705        self.visible
706    }
707
708    fn set_visible(&mut self, visible: bool) {
709        self.visible = visible;
710    }
711
712    fn calculate_size(&self, style: &LegendStyle<C>) -> Size {
713        let text_width = self.label.len() as u32 * style.text.char_width;
714        let total_width = style.spacing.symbol_width + style.spacing.symbol_text_gap + text_width;
715        Size::new(total_width, style.text.line_height)
716    }
717
718    fn render_symbol<D>(
719        &self,
720        bounds: Rectangle,
721        _style: &SymbolStyle<C>,
722        target: &mut D,
723    ) -> ChartResult<()>
724    where
725        D: DrawTarget<Color = C>,
726    {
727        use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle as EgRectangle};
728
729        match &self.entry_type {
730            LegendEntryType::Line { color, .. } => {
731                // Draw a small line segment
732                let line_y = bounds.top_left.y + bounds.size.height as i32 / 2;
733                let line_start = Point::new(bounds.top_left.x + 2, line_y);
734                let line_end = Point::new(bounds.top_left.x + bounds.size.width as i32 - 2, line_y);
735
736                use embedded_graphics::primitives::Line;
737                Line::new(line_start, line_end)
738                    .into_styled(PrimitiveStyle::with_stroke(*color, 1))
739                    .draw(target)
740                    .map_err(|_| crate::error::ChartError::RenderingError)?;
741            }
742            LegendEntryType::Bar { color, .. } | LegendEntryType::Pie { color, .. } => {
743                // Draw a small rectangle
744                let rect_size = Size::new(bounds.size.width.min(16), bounds.size.height.min(12));
745                let rect_pos = Point::new(
746                    bounds.top_left.x + (bounds.size.width as i32 - rect_size.width as i32) / 2,
747                    bounds.top_left.y + (bounds.size.height as i32 - rect_size.height as i32) / 2,
748                );
749
750                EgRectangle::new(rect_pos, rect_size)
751                    .into_styled(PrimitiveStyle::with_fill(*color))
752                    .draw(target)
753                    .map_err(|_| crate::error::ChartError::RenderingError)?;
754            }
755            LegendEntryType::Custom { color, shape, size } => {
756                let symbol_size = (*size).min(bounds.size.width).min(bounds.size.height);
757                let center = Point::new(
758                    bounds.top_left.x + bounds.size.width as i32 / 2,
759                    bounds.top_left.y + bounds.size.height as i32 / 2,
760                );
761
762                match shape {
763                    SymbolShape::Circle => {
764                        Circle::with_center(center, symbol_size)
765                            .into_styled(PrimitiveStyle::with_fill(*color))
766                            .draw(target)
767                            .map_err(|_| crate::error::ChartError::RenderingError)?;
768                    }
769                    SymbolShape::Square => {
770                        let rect_size = Size::new(symbol_size, symbol_size);
771                        let rect_pos = Point::new(
772                            center.x - symbol_size as i32 / 2,
773                            center.y - symbol_size as i32 / 2,
774                        );
775                        EgRectangle::new(rect_pos, rect_size)
776                            .into_styled(PrimitiveStyle::with_fill(*color))
777                            .draw(target)
778                            .map_err(|_| crate::error::ChartError::RenderingError)?;
779                    }
780                    _ => {
781                        // For other shapes, draw a circle as fallback
782                        Circle::with_center(center, symbol_size)
783                            .into_styled(PrimitiveStyle::with_fill(*color))
784                            .draw(target)
785                            .map_err(|_| crate::error::ChartError::RenderingError)?;
786                    }
787                }
788            }
789        }
790
791        Ok(())
792    }
793}
794
795// Similar implementations for CompactLegend and CustomLegend would follow the same pattern
796// but with their respective constraints and features
797
798impl Default for LegendOrientation {
799    fn default() -> Self {
800        Self::Vertical
801    }
802}
803
804impl Default for CustomLayoutParams {
805    fn default() -> Self {
806        Self {
807            entry_spacing: 8,
808            symbol_size: 16,
809            text_offset: Point::new(20, 0),
810            auto_layout: true,
811        }
812    }
813}
814
815// Implementation of LegendEntry trait for CustomLegendEntry
816impl<C: PixelColor> LegendEntry<C> for CustomLegendEntry<C> {
817    fn label(&self) -> &str {
818        &self.label
819    }
820
821    fn set_label(&mut self, label: &str) -> ChartResult<()> {
822        self.label =
823            heapless::String::try_from(label).map_err(|_| ChartError::ConfigurationError)?;
824        Ok(())
825    }
826
827    fn entry_type(&self) -> &LegendEntryType<C> {
828        &self.entry_type
829    }
830
831    fn set_entry_type(&mut self, entry_type: LegendEntryType<C>) {
832        self.entry_type = entry_type;
833    }
834
835    fn is_visible(&self) -> bool {
836        self.visible
837    }
838
839    fn set_visible(&mut self, visible: bool) {
840        self.visible = visible;
841    }
842
843    fn calculate_size(&self, style: &LegendStyle<C>) -> Size {
844        let text_width = self.label.len() as u32 * style.text.char_width;
845        let total_width = style.spacing.symbol_width + style.spacing.symbol_text_gap + text_width;
846
847        // Use size override if available
848        if let Some(size_override) = self.size_override {
849            size_override
850        } else {
851            Size::new(total_width, style.text.line_height)
852        }
853    }
854
855    fn render_symbol<D>(
856        &self,
857        bounds: Rectangle,
858        _style: &SymbolStyle<C>,
859        target: &mut D,
860    ) -> ChartResult<()>
861    where
862        D: DrawTarget<Color = C>,
863    {
864        use embedded_graphics::primitives::{
865            Circle, Line, PrimitiveStyle, Rectangle as EgRectangle,
866        };
867
868        match &self.entry_type {
869            LegendEntryType::Line { color, .. } => {
870                let line_y = bounds.top_left.y + bounds.size.height as i32 / 2;
871                let line_start = Point::new(bounds.top_left.x + 2, line_y);
872                let line_end = Point::new(bounds.top_left.x + bounds.size.width as i32 - 2, line_y);
873
874                Line::new(line_start, line_end)
875                    .into_styled(PrimitiveStyle::with_stroke(*color, 1))
876                    .draw(target)
877                    .map_err(|_| ChartError::RenderingError)?;
878            }
879            LegendEntryType::Bar { color, .. } | LegendEntryType::Pie { color, .. } => {
880                let rect_size = Size::new(bounds.size.width.min(16), bounds.size.height.min(12));
881                let rect_pos = Point::new(
882                    bounds.top_left.x + (bounds.size.width as i32 - rect_size.width as i32) / 2,
883                    bounds.top_left.y + (bounds.size.height as i32 - rect_size.height as i32) / 2,
884                );
885
886                EgRectangle::new(rect_pos, rect_size)
887                    .into_styled(PrimitiveStyle::with_fill(*color))
888                    .draw(target)
889                    .map_err(|_| ChartError::RenderingError)?;
890            }
891            LegendEntryType::Custom { color, shape, size } => {
892                let symbol_size = (*size).min(bounds.size.width).min(bounds.size.height);
893                let center = Point::new(
894                    bounds.top_left.x + bounds.size.width as i32 / 2,
895                    bounds.top_left.y + bounds.size.height as i32 / 2,
896                );
897
898                match shape {
899                    SymbolShape::Circle => {
900                        Circle::with_center(center, symbol_size)
901                            .into_styled(PrimitiveStyle::with_fill(*color))
902                            .draw(target)
903                            .map_err(|_| ChartError::RenderingError)?;
904                    }
905                    SymbolShape::Square => {
906                        let half_size = symbol_size / 2;
907                        let rect_pos =
908                            Point::new(center.x - half_size as i32, center.y - half_size as i32);
909                        EgRectangle::new(rect_pos, Size::new(symbol_size, symbol_size))
910                            .into_styled(PrimitiveStyle::with_fill(*color))
911                            .draw(target)
912                            .map_err(|_| ChartError::RenderingError)?;
913                    }
914                    _ => {
915                        // For other shapes, default to circle
916                        Circle::with_center(center, symbol_size)
917                            .into_styled(PrimitiveStyle::with_fill(*color))
918                            .draw(target)
919                            .map_err(|_| ChartError::RenderingError)?;
920                    }
921                }
922            }
923        }
924
925        Ok(())
926    }
927}