embedded_charts/chart/
bar.rs

1//! Bar chart implementation with no_std compatibility.
2//!
3//! This module provides a comprehensive bar chart implementation optimized for embedded systems.
4//! It supports both vertical and horizontal orientations, multiple styling options, and efficient
5//! rendering while maintaining memory efficiency suitable for resource-constrained environments.
6//!
7//! # Features
8//!
9//! - **Dual orientation**: Support for both vertical and horizontal bar charts
10//! - **Flexible bar width**: Fixed width, proportional width, or automatic sizing
11//! - **Multi-color support**: Up to 16 different bar colors with automatic cycling
12//! - **Customizable spacing**: Configurable spacing between bars
13//! - **Border styling**: Optional borders with customizable styles
14//! - **Stacked bars**: Support for stacked bar charts (feature-gated)
15//! - **Memory efficient**: Static allocation with compile-time bounds
16//!
17//! # Basic Usage
18//!
19//! ```rust
20//! use embedded_charts::prelude::*;
21//! use embedded_graphics::pixelcolor::Rgb565;
22//!
23//! // Create sample data
24//! let mut data: StaticDataSeries<Point2D, 256> = StaticDataSeries::new();
25//! data.push(Point2D::new(0.0, 10.0))?;
26//! data.push(Point2D::new(1.0, 20.0))?;
27//! data.push(Point2D::new(2.0, 15.0))?;
28//!
29//! // Create a basic bar chart
30//! let chart = BarChart::builder()
31//!     .orientation(BarOrientation::Vertical)
32//!     .bar_width(BarWidth::Fixed(20))
33//!     .colors(&[Rgb565::BLUE])
34//!     .build()?;
35//!
36//! // Configure the chart
37//! let config: ChartConfig<Rgb565> = ChartConfig::default();
38//! let viewport = Rectangle::new(Point::zero(), Size::new(320, 240));
39//!
40//! // Render to display (display would be provided by your embedded target)
41//! // chart.draw(&data, &config, viewport, &mut display)?;
42//! # Ok::<(), embedded_charts::error::ChartError>(())
43//! ```
44//!
45//! # Advanced Styling
46//!
47//! ```rust
48//! use embedded_charts::prelude::*;
49//! use embedded_graphics::pixelcolor::Rgb565;
50//!
51//! let line_style = LineStyle::solid(Rgb565::BLACK);
52//! let border_style = BorderStyle::new(line_style);
53//! let chart = BarChart::builder()
54//!     .orientation(BarOrientation::Horizontal)
55//!     .bar_width(BarWidth::Percentage(0.8)) // 80% of available space
56//!     .spacing(5)
57//!     .with_border(border_style)
58//!     .colors(&[Rgb565::BLUE, Rgb565::RED, Rgb565::GREEN])
59//!     .build()?;
60//! # Ok::<(), embedded_charts::error::ChartError>(())
61//! ```
62//!
63//! # Multi-Series Bar Charts
64//!
65//! ```rust
66//! use embedded_charts::prelude::*;
67//! use embedded_graphics::pixelcolor::Rgb565;
68//!
69//! // Create multi-series data
70//! let mut multi_series: MultiSeries<Point2D, 8, 256> = MultiSeries::new();
71//! let series1 = data_points![(0.0, 10.0), (1.0, 15.0), (2.0, 12.0)];
72//! let series2 = data_points![(0.0, 8.0), (1.0, 18.0), (2.0, 14.0)];
73//!
74//! multi_series.add_series(series1)?;
75//! multi_series.add_series(series2)?;
76//!
77//! // Create chart with multiple colors
78//! let chart = BarChart::builder()
79//!     .bar_width(BarWidth::Auto)
80//!     .spacing(2)
81//!     .colors(&[Rgb565::BLUE, Rgb565::RED])
82//!     .build()?;
83//! # Ok::<(), embedded_charts::error::ChartError>(())
84//! ```
85
86use crate::chart::traits::{Chart, ChartBuilder, ChartConfig};
87use crate::data::{DataBounds, DataPoint, DataSeries};
88use crate::error::{ChartError, ChartResult};
89use crate::style::BorderStyle;
90use embedded_graphics::{
91    draw_target::DrawTarget,
92    prelude::*,
93    primitives::{PrimitiveStyle, Rectangle},
94};
95use heapless::Vec;
96
97/// Bar chart implementation for displaying categorical data.
98///
99/// A bar chart displays data using rectangular bars with lengths proportional to the values
100/// they represent. This implementation supports both vertical and horizontal orientations,
101/// making it suitable for various data visualization needs on embedded displays.
102///
103/// # Features
104///
105/// - Configurable bar orientation (vertical/horizontal)
106/// - Flexible bar width options (fixed, proportional, automatic)
107/// - Multi-color support with automatic color cycling
108/// - Customizable spacing and borders
109/// - Support for stacked bars (feature-gated)
110/// - Memory-efficient static allocation
111///
112/// # Memory Usage
113///
114/// The bar chart uses static allocation with:
115/// - Up to 16 bar colors stored in a heapless vector
116/// - Screen coordinate calculations for bar positioning
117/// - Efficient rendering with minimal temporary storage
118///
119/// # Examples
120///
121/// Basic vertical bar chart:
122/// ```rust
123/// use embedded_charts::prelude::*;
124/// use embedded_graphics::pixelcolor::Rgb565;
125///
126/// let chart: BarChart<Rgb565> = BarChart::new();
127/// let mut data: StaticDataSeries<Point2D, 256> = StaticDataSeries::new();
128/// data.push(Point2D::new(0.0, 10.0))?;
129/// data.push(Point2D::new(1.0, 20.0))?;
130/// # Ok::<(), embedded_charts::error::DataError>(())
131/// ```
132///
133/// Horizontal bar chart with custom styling:
134/// ```rust
135/// use embedded_charts::prelude::*;
136/// use embedded_graphics::pixelcolor::Rgb565;
137///
138/// let chart = BarChart::builder()
139///     .orientation(BarOrientation::Horizontal)
140///     .bar_width(BarWidth::Fixed(25))
141///     .spacing(3)
142///     .colors(&[Rgb565::GREEN])
143///     .build()?;
144/// # Ok::<(), embedded_charts::error::ChartError>(())
145/// ```
146#[derive(Debug, Clone)]
147pub struct BarChart<C: PixelColor> {
148    style: BarChartStyle<C>,
149    config: ChartConfig<C>,
150    orientation: BarOrientation,
151}
152
153/// Style configuration for bar charts.
154///
155/// This structure contains all visual styling options for bar charts,
156/// including colors, dimensions, spacing, and border styles.
157///
158/// # Examples
159///
160/// ```rust
161/// use embedded_charts::prelude::*;
162/// use embedded_graphics::pixelcolor::Rgb565;
163///
164/// let mut colors = heapless::Vec::new();
165/// colors.push(Rgb565::BLUE).unwrap();
166/// colors.push(Rgb565::RED).unwrap();
167///
168/// let style = BarChartStyle {
169///     bar_colors: colors,
170///     bar_width: BarWidth::Fixed(20),
171///     spacing: 5,
172///     border: None,
173///     stacked: false,
174/// };
175/// ```
176#[derive(Debug, Clone)]
177pub struct BarChartStyle<C: PixelColor> {
178    /// Colors for the bars.
179    ///
180    /// The chart cycles through these colors for multiple data series.
181    /// Maximum of 16 colors supported for memory efficiency.
182    pub bar_colors: Vec<C, 16>,
183    /// Width configuration for bars.
184    ///
185    /// Determines how bar widths are calculated based on available space.
186    pub bar_width: BarWidth,
187    /// Spacing between bars in pixels.
188    ///
189    /// This spacing is applied between individual bars and between groups
190    /// in multi-series charts.
191    pub spacing: u32,
192    /// Optional border style for bars.
193    ///
194    /// When `Some`, draws borders around each bar with the specified style.
195    /// When `None`, bars are drawn without borders.
196    pub border: Option<BorderStyle<C>>,
197    /// Whether bars should be stacked.
198    ///
199    /// When `true`, multiple data series are stacked on top of each other.
200    /// When `false`, series are displayed side by side.
201    pub stacked: bool,
202}
203
204/// Bar orientation options.
205///
206/// Determines whether bars extend vertically (from bottom to top) or
207/// horizontally (from left to right).
208///
209/// # Examples
210///
211/// ```rust
212/// use embedded_charts::prelude::*;
213///
214/// // Vertical bars (traditional bar chart)
215/// let vertical = BarOrientation::Vertical;
216///
217/// // Horizontal bars (useful for long category names)
218/// let horizontal = BarOrientation::Horizontal;
219/// ```
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub enum BarOrientation {
222    /// Vertical bars extending from bottom to top.
223    ///
224    /// This is the traditional bar chart orientation where bars grow upward
225    /// from a baseline. Best for time-series data or when category names are short.
226    Vertical,
227    /// Horizontal bars extending from left to right.
228    ///
229    /// Useful when category names are long or when you want to emphasize
230    /// the comparison between values. The baseline is on the left side.
231    Horizontal,
232}
233
234/// Bar width configuration options.
235///
236/// Determines how the width of bars is calculated based on the available
237/// chart space and the number of data points.
238///
239/// # Examples
240///
241/// ```rust
242/// use embedded_charts::prelude::*;
243///
244/// // Fixed width bars
245/// let fixed = BarWidth::Fixed(20); // 20 pixels wide
246///
247/// // Percentage width (80% of available space per bar)
248/// let percentage = BarWidth::Percentage(0.8);
249///
250/// // Automatic width calculation
251/// let auto = BarWidth::Auto;
252/// ```
253#[derive(Debug, Clone, Copy, PartialEq)]
254pub enum BarWidth {
255    /// Fixed width in pixels.
256    ///
257    /// Each bar will have exactly the specified width regardless of the
258    /// available space or number of data points. This provides consistent
259    /// bar appearance but may cause bars to overlap or leave unused space.
260    ///
261    /// # Examples
262    ///
263    /// ```rust
264    /// use embedded_charts::prelude::*;
265    ///
266    /// let width = BarWidth::Fixed(25); // 25 pixels wide
267    /// ```
268    Fixed(u32),
269    /// Percentage of available space per bar (0.0 to 1.0).
270    ///
271    /// Each bar will occupy the specified percentage of the space allocated
272    /// to it. For example, 0.8 means each bar uses 80% of its allocated space,
273    /// leaving 20% for spacing.
274    ///
275    /// # Examples
276    ///
277    /// ```rust
278    /// use embedded_charts::prelude::*;
279    ///
280    /// let width = BarWidth::Percentage(0.7); // 70% of allocated space
281    /// ```
282    Percentage(f32),
283    /// Automatic width calculation based on available space and data count.
284    ///
285    /// The chart automatically calculates optimal bar width based on the
286    /// viewport size, number of data points, and spacing requirements.
287    /// This provides the best balance between bar visibility and spacing.
288    ///
289    /// # Examples
290    ///
291    /// ```rust
292    /// use embedded_charts::prelude::*;
293    ///
294    /// let width = BarWidth::Auto; // Automatic calculation
295    /// ```
296    Auto,
297}
298
299impl<C: PixelColor> BarChart<C>
300where
301    C: From<embedded_graphics::pixelcolor::Rgb565>,
302{
303    /// Create a new bar chart with default styling.
304    ///
305    /// This creates a bar chart with:
306    /// - Vertical orientation
307    /// - Single blue bar color
308    /// - Automatic bar width
309    /// - 5-pixel spacing between bars
310    /// - No borders
311    /// - Default margins (10 pixels on all sides)
312    ///
313    /// # Examples
314    ///
315    /// ```rust
316    /// use embedded_charts::prelude::*;
317    /// use embedded_graphics::pixelcolor::Rgb565;
318    ///
319    /// let chart: BarChart<Rgb565> = BarChart::new();
320    /// ```
321    pub fn new() -> Self {
322        Self {
323            style: BarChartStyle::default(),
324            config: ChartConfig::default(),
325            orientation: BarOrientation::Vertical,
326        }
327    }
328
329    /// Create a builder for configuring the bar chart.
330    ///
331    /// The builder pattern provides a fluent interface for configuring
332    /// all aspects of the bar chart before creation.
333    ///
334    /// # Examples
335    ///
336    /// ```rust
337    /// use embedded_charts::prelude::*;
338    /// use embedded_graphics::pixelcolor::Rgb565;
339    ///
340    /// let chart = BarChart::builder()
341    ///     .orientation(BarOrientation::Horizontal)
342    ///     .bar_width(BarWidth::Fixed(30))
343    ///     .spacing(8)
344    ///     .colors(&[Rgb565::GREEN])
345    ///     .build()?;
346    /// # Ok::<(), embedded_charts::error::ChartError>(())
347    /// ```
348    pub fn builder() -> BarChartBuilder<C> {
349        BarChartBuilder::new()
350    }
351
352    /// Set the bar chart style configuration.
353    ///
354    /// This replaces the entire style configuration with the provided one.
355    /// Use the builder pattern for more granular control.
356    ///
357    /// # Arguments
358    ///
359    /// * `style` - The new bar chart style configuration
360    ///
361    /// # Examples
362    ///
363    /// ```rust
364    /// use embedded_charts::prelude::*;
365    /// use embedded_graphics::pixelcolor::Rgb565;
366    ///
367    /// let mut chart: BarChart<Rgb565> = BarChart::new();
368    /// let mut colors = heapless::Vec::new();
369    /// colors.push(Rgb565::BLUE).unwrap();
370    /// colors.push(Rgb565::RED).unwrap();
371    ///
372    /// let style = BarChartStyle {
373    ///     bar_colors: colors,
374    ///     bar_width: BarWidth::Fixed(25),
375    ///     spacing: 3,
376    ///     border: None,
377    ///     stacked: false,
378    /// };
379    /// chart.set_style(style);
380    /// ```
381    pub fn set_style(&mut self, style: BarChartStyle<C>) {
382        self.style = style;
383    }
384
385    /// Get the current bar chart style configuration.
386    ///
387    /// Returns a reference to the current style configuration,
388    /// allowing inspection of current settings.
389    ///
390    /// # Returns
391    ///
392    /// A reference to the current `BarChartStyle`
393    pub fn style(&self) -> &BarChartStyle<C> {
394        &self.style
395    }
396
397    /// Set the chart configuration.
398    ///
399    /// This includes general chart settings like title, background color,
400    /// margins, and grid visibility.
401    ///
402    /// # Arguments
403    ///
404    /// * `config` - The new chart configuration
405    pub fn set_config(&mut self, config: ChartConfig<C>) {
406        self.config = config;
407    }
408
409    /// Get the chart configuration
410    pub fn config(&self) -> &ChartConfig<C> {
411        &self.config
412    }
413
414    /// Set the bar orientation
415    pub fn set_orientation(&mut self, orientation: BarOrientation) {
416        self.orientation = orientation;
417    }
418
419    /// Get the bar orientation
420    pub fn orientation(&self) -> BarOrientation {
421        self.orientation
422    }
423
424    /// Calculate bar dimensions and positions
425    fn calculate_bar_layout(
426        &self,
427        data: &crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
428        data_bounds: &DataBounds<f32, f32>,
429        viewport: Rectangle,
430    ) -> ChartResult<Vec<Rectangle, 256>> {
431        let mut bars = Vec::new();
432        let draw_area = self.config.margins.apply_to(viewport);
433
434        let data_count = data.len();
435        if data_count == 0 {
436            return Ok(bars);
437        }
438
439        // Calculate bar width
440        let bar_width = match self.style.bar_width {
441            BarWidth::Fixed(width) => width,
442            BarWidth::Percentage(pct) => {
443                let available_width = match self.orientation {
444                    BarOrientation::Vertical => draw_area.size.width,
445                    BarOrientation::Horizontal => draw_area.size.height,
446                };
447                (available_width as f32 * pct.clamp(0.0, 1.0)) as u32
448            }
449            BarWidth::Auto => {
450                let available_width = match self.orientation {
451                    BarOrientation::Vertical => draw_area.size.width,
452                    BarOrientation::Horizontal => draw_area.size.height,
453                };
454                let total_spacing = self.style.spacing * (data_count as u32).saturating_sub(1);
455                let calculated_width =
456                    (available_width.saturating_sub(total_spacing)) / data_count as u32;
457                // Ensure minimum bar width for visibility
458                calculated_width.max(5)
459            }
460        };
461
462        // Calculate positions and sizes for each bar
463        let mut current_pos = 0;
464        for point in data.iter() {
465            let bar_rect = match self.orientation {
466                BarOrientation::Vertical => {
467                    let x = draw_area.top_left.x + current_pos as i32;
468                    let data_y: f32 = point.y();
469                    let min_y: f32 = data_bounds.min_y;
470                    let max_y: f32 = data_bounds.max_y;
471
472                    // Normalize Y value (0.0 to 1.0)
473                    let norm_y = if max_y > min_y {
474                        (data_y - min_y) / (max_y - min_y)
475                    } else {
476                        0.5
477                    };
478
479                    // Ensure minimum bar height for visibility
480                    let bar_height = ((norm_y * draw_area.size.height as f32) as u32).max(1);
481                    let y = draw_area.top_left.y + draw_area.size.height as i32 - bar_height as i32;
482
483                    Rectangle::new(Point::new(x, y), Size::new(bar_width, bar_height))
484                }
485                BarOrientation::Horizontal => {
486                    let y = draw_area.top_left.y + current_pos as i32;
487                    let data_y: f32 = point.y();
488                    let min_y: f32 = data_bounds.min_y;
489                    let max_y: f32 = data_bounds.max_y;
490
491                    // Normalize Y value (0.0 to 1.0)
492                    let norm_y = if max_y > min_y {
493                        (data_y - min_y) / (max_y - min_y)
494                    } else {
495                        0.5
496                    };
497
498                    // Ensure minimum bar width for visibility
499                    let bar_width_horizontal =
500                        ((norm_y * draw_area.size.width as f32) as u32).max(1);
501                    let x = draw_area.top_left.x;
502
503                    Rectangle::new(Point::new(x, y), Size::new(bar_width_horizontal, bar_width))
504                }
505            };
506
507            bars.push(bar_rect).map_err(|_| ChartError::MemoryFull)?;
508            current_pos += bar_width + self.style.spacing;
509        }
510
511        Ok(bars)
512    }
513
514    /// Draw a single bar
515    fn draw_bar<D>(
516        &self,
517        bar_rect: Rectangle,
518        color_index: usize,
519        target: &mut D,
520    ) -> ChartResult<()>
521    where
522        D: DrawTarget<Color = C>,
523    {
524        // Get bar color (cycle through available colors)
525        let bar_color = if !self.style.bar_colors.is_empty() {
526            self.style.bar_colors[color_index % self.style.bar_colors.len()]
527        } else {
528            return Err(ChartError::InvalidConfiguration);
529        };
530
531        // Draw filled bar directly
532        bar_rect
533            .into_styled(PrimitiveStyle::with_fill(bar_color))
534            .draw(target)
535            .map_err(|_| ChartError::RenderingError)?;
536
537        // Draw border if specified
538        if let Some(border) = &self.style.border {
539            if border.visible {
540                bar_rect
541                    .into_styled(PrimitiveStyle::with_stroke(
542                        border.line.color,
543                        border.line.width,
544                    ))
545                    .draw(target)
546                    .map_err(|_| ChartError::RenderingError)?;
547            }
548        }
549
550        Ok(())
551    }
552}
553
554impl<C: PixelColor> Default for BarChart<C>
555where
556    C: From<embedded_graphics::pixelcolor::Rgb565>,
557{
558    fn default() -> Self {
559        Self::new()
560    }
561}
562
563impl<C: PixelColor> Chart<C> for BarChart<C>
564where
565    C: From<embedded_graphics::pixelcolor::Rgb565>,
566{
567    type Data = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
568    type Config = ChartConfig<C>;
569
570    fn draw<D>(
571        &self,
572        data: &Self::Data,
573        config: &Self::Config,
574        viewport: Rectangle,
575        target: &mut D,
576    ) -> ChartResult<()>
577    where
578        D: DrawTarget<Color = C>,
579        Self::Data: DataSeries,
580        <Self::Data as DataSeries>::Item: DataPoint,
581        <<Self::Data as DataSeries>::Item as DataPoint>::X: Into<f32> + Copy + PartialOrd,
582        <<Self::Data as DataSeries>::Item as DataPoint>::Y: Into<f32> + Copy + PartialOrd,
583    {
584        if data.is_empty() {
585            return Err(ChartError::InsufficientData);
586        }
587
588        // Draw background if specified
589        if let Some(bg_color) = config.background_color {
590            Rectangle::new(viewport.top_left, viewport.size)
591                .into_styled(PrimitiveStyle::with_fill(bg_color))
592                .draw(target)
593                .map_err(|_| ChartError::RenderingError)?;
594        }
595
596        // Calculate data bounds
597        let data_bounds = data.bounds()?;
598
599        // Calculate bar layout
600        let bars = self.calculate_bar_layout(data, &data_bounds, viewport)?;
601
602        // Draw each bar
603        for (index, bar_rect) in bars.iter().enumerate() {
604            self.draw_bar(*bar_rect, index, target)?;
605        }
606
607        Ok(())
608    }
609}
610
611impl<C: PixelColor> Default for BarChartStyle<C>
612where
613    C: From<embedded_graphics::pixelcolor::Rgb565>,
614{
615    fn default() -> Self {
616        let mut colors = Vec::new();
617        let _ = colors.push(embedded_graphics::pixelcolor::Rgb565::BLUE.into());
618        let _ = colors.push(embedded_graphics::pixelcolor::Rgb565::RED.into());
619        let _ = colors.push(embedded_graphics::pixelcolor::Rgb565::GREEN.into());
620        let _ = colors.push(embedded_graphics::pixelcolor::Rgb565::YELLOW.into());
621
622        Self {
623            bar_colors: colors,
624            bar_width: BarWidth::Auto,
625            spacing: 2,
626            border: None,
627            stacked: false,
628        }
629    }
630}
631
632/// Builder for bar charts
633#[derive(Debug)]
634pub struct BarChartBuilder<C: PixelColor> {
635    style: BarChartStyle<C>,
636    config: ChartConfig<C>,
637    orientation: BarOrientation,
638}
639
640impl<C: PixelColor> BarChartBuilder<C>
641where
642    C: From<embedded_graphics::pixelcolor::Rgb565>,
643{
644    /// Create a new bar chart builder
645    pub fn new() -> Self {
646        Self {
647            style: BarChartStyle::default(),
648            config: ChartConfig::default(),
649            orientation: BarOrientation::Vertical,
650        }
651    }
652
653    /// Set the bar orientation
654    pub fn orientation(mut self, orientation: BarOrientation) -> Self {
655        self.orientation = orientation;
656        self
657    }
658
659    /// Set bar colors
660    pub fn colors(mut self, colors: &[C]) -> Self {
661        self.style.bar_colors.clear();
662        for &color in colors {
663            if self.style.bar_colors.push(color).is_err() {
664                break; // Reached capacity
665            }
666        }
667        self
668    }
669
670    /// Set bar width
671    pub fn bar_width(mut self, width: BarWidth) -> Self {
672        self.style.bar_width = width;
673        self
674    }
675
676    /// Set spacing between bars
677    pub fn spacing(mut self, spacing: u32) -> Self {
678        self.style.spacing = spacing;
679        self
680    }
681
682    /// Add a border to bars
683    pub fn with_border(mut self, border: BorderStyle<C>) -> Self {
684        self.style.border = Some(border);
685        self
686    }
687
688    /// Enable stacked bars
689    pub fn stacked(mut self, stacked: bool) -> Self {
690        self.style.stacked = stacked;
691        self
692    }
693
694    /// Set the chart title
695    pub fn with_title(mut self, title: &str) -> Self {
696        if let Ok(title_string) = heapless::String::try_from(title) {
697            self.config.title = Some(title_string);
698        }
699        self
700    }
701
702    /// Set the background color
703    pub fn background_color(mut self, color: C) -> Self {
704        self.config.background_color = Some(color);
705        self
706    }
707}
708
709impl<C: PixelColor> ChartBuilder<C> for BarChartBuilder<C>
710where
711    C: From<embedded_graphics::pixelcolor::Rgb565>,
712{
713    type Chart = BarChart<C>;
714    type Error = ChartError;
715
716    fn build(self) -> Result<Self::Chart, Self::Error> {
717        Ok(BarChart {
718            style: self.style,
719            config: self.config,
720            orientation: self.orientation,
721        })
722    }
723}
724
725impl<C: PixelColor> Default for BarChartBuilder<C>
726where
727    C: From<embedded_graphics::pixelcolor::Rgb565>,
728{
729    fn default() -> Self {
730        Self::new()
731    }
732}
733
734#[cfg(test)]
735mod tests {
736    use super::*;
737    use embedded_graphics::pixelcolor::Rgb565;
738
739    #[test]
740    fn test_bar_chart_creation() {
741        let chart: BarChart<Rgb565> = BarChart::new();
742        assert_eq!(chart.orientation(), BarOrientation::Vertical);
743        assert!(!chart.style().stacked);
744    }
745
746    #[test]
747    fn test_bar_chart_builder() {
748        let chart: BarChart<Rgb565> = BarChart::builder()
749            .orientation(BarOrientation::Horizontal)
750            .colors(&[Rgb565::RED, Rgb565::BLUE])
751            .bar_width(BarWidth::Fixed(30))
752            .spacing(5)
753            .with_title("Test Bar Chart")
754            .build()
755            .unwrap();
756
757        assert_eq!(chart.orientation(), BarOrientation::Horizontal);
758        assert_eq!(chart.style().bar_colors.len(), 2);
759        assert_eq!(chart.style().spacing, 5);
760        assert_eq!(
761            chart.config().title.as_ref().map(|s| s.as_str()),
762            Some("Test Bar Chart")
763        );
764    }
765
766    #[test]
767    fn test_bar_width_types() {
768        assert_eq!(BarWidth::Fixed(20), BarWidth::Fixed(20));
769
770        let percentage = BarWidth::Percentage(0.8);
771        if let BarWidth::Percentage(pct) = percentage {
772            assert_eq!(pct, 0.8);
773        }
774
775        assert_eq!(BarWidth::Auto, BarWidth::Auto);
776    }
777}
778
779/// Animated bar chart that extends BarChart with animation capabilities
780#[cfg(feature = "animations")]
781#[derive(Debug, Clone)]
782pub struct AnimatedBarChart<C: PixelColor> {
783    /// Base bar chart
784    base_chart: BarChart<C>,
785    /// Current animated data (interpolated values)
786    current_data: Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>>,
787}
788
789#[cfg(feature = "animations")]
790impl<C: PixelColor> AnimatedBarChart<C>
791where
792    C: From<embedded_graphics::pixelcolor::Rgb565>,
793{
794    /// Create a new animated bar chart
795    pub fn new() -> Self {
796        Self {
797            base_chart: BarChart::new(),
798            current_data: None,
799        }
800    }
801
802    /// Create a builder for configuring the animated bar chart
803    pub fn builder() -> AnimatedBarChartBuilder<C> {
804        AnimatedBarChartBuilder::new()
805    }
806
807    /// Set the bar chart style
808    pub fn set_style(&mut self, style: BarChartStyle<C>) {
809        self.base_chart.set_style(style);
810    }
811
812    /// Get the current bar chart style
813    pub fn style(&self) -> &BarChartStyle<C> {
814        self.base_chart.style()
815    }
816
817    /// Set the chart configuration
818    pub fn set_config(&mut self, config: ChartConfig<C>) {
819        self.base_chart.set_config(config);
820    }
821
822    /// Get the chart configuration
823    pub fn config(&self) -> &ChartConfig<C> {
824        self.base_chart.config()
825    }
826
827    /// Set the bar orientation
828    pub fn set_orientation(&mut self, orientation: BarOrientation) {
829        self.base_chart.set_orientation(orientation);
830    }
831
832    /// Get the bar orientation
833    pub fn orientation(&self) -> BarOrientation {
834        self.base_chart.orientation()
835    }
836
837    /// Get the current animated data or fallback to empty series
838    fn get_render_data(
839        &self,
840    ) -> crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256> {
841        self.current_data.clone().unwrap_or_default()
842    }
843
844    /// Interpolate between two data series based on animation progress
845    #[allow(dead_code)]
846    fn interpolate_data(
847        &self,
848        from_data: &crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
849        to_data: &crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
850        progress: f32,
851    ) -> ChartResult<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>> {
852        let mut result = crate::data::series::StaticDataSeries::new();
853
854        // Handle different data sizes by taking the minimum
855        let min_len = from_data.len().min(to_data.len());
856
857        for i in 0..min_len {
858            if let (Some(from_point), Some(to_point)) = (from_data.get(i), to_data.get(i)) {
859                let interpolated_x = from_point.x() + (to_point.x() - from_point.x()) * progress;
860                let interpolated_y = from_point.y() + (to_point.y() - from_point.y()) * progress;
861
862                result
863                    .push(crate::data::point::Point2D::new(
864                        interpolated_x,
865                        interpolated_y,
866                    ))
867                    .map_err(|_| crate::error::ChartError::MemoryFull)?;
868            }
869        }
870
871        Ok(result)
872    }
873}
874
875#[cfg(feature = "animations")]
876impl<C: PixelColor> Default for AnimatedBarChart<C>
877where
878    C: From<embedded_graphics::pixelcolor::Rgb565>,
879{
880    fn default() -> Self {
881        Self::new()
882    }
883}
884
885#[cfg(feature = "animations")]
886impl<C: PixelColor> Chart<C> for AnimatedBarChart<C>
887where
888    C: From<embedded_graphics::pixelcolor::Rgb565>,
889{
890    type Data = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
891    type Config = ChartConfig<C>;
892
893    fn draw<D>(
894        &self,
895        data: &Self::Data,
896        config: &Self::Config,
897        viewport: Rectangle,
898        target: &mut D,
899    ) -> ChartResult<()>
900    where
901        D: DrawTarget<Color = C>,
902        Self::Data: crate::data::DataSeries,
903        <Self::Data as crate::data::DataSeries>::Item: crate::data::DataPoint,
904        <<Self::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::X:
905            Into<f32> + Copy + PartialOrd,
906        <<Self::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::Y:
907            Into<f32> + Copy + PartialOrd,
908    {
909        // Use animated data if available, otherwise use provided data
910        if self.current_data.is_some() {
911            let render_data = self.get_render_data();
912            self.base_chart.draw(&render_data, config, viewport, target)
913        } else {
914            self.base_chart.draw(data, config, viewport, target)
915        }
916    }
917}
918
919#[cfg(feature = "animations")]
920impl<C: PixelColor> crate::chart::traits::AnimatedChart<C> for AnimatedBarChart<C>
921where
922    C: From<embedded_graphics::pixelcolor::Rgb565>,
923{
924    type AnimatedData = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
925
926    fn draw_animated<D>(
927        &self,
928        data: &Self::Data,
929        config: &Self::Config,
930        viewport: embedded_graphics::primitives::Rectangle,
931        target: &mut D,
932        _progress: crate::animation::Progress,
933    ) -> ChartResult<()>
934    where
935        D: embedded_graphics::draw_target::DrawTarget<Color = C>,
936    {
937        // Use the provided data which should already be interpolated by the caller
938        self.base_chart.draw(data, config, viewport, target)
939    }
940
941    fn create_transition_animator(
942        &self,
943        from_data: Self::AnimatedData,
944        to_data: Self::AnimatedData,
945        easing: crate::animation::EasingFunction,
946    ) -> crate::animation::ChartAnimator<Self::AnimatedData> {
947        crate::animation::ChartAnimator::new(from_data, to_data, easing)
948    }
949
950    fn extract_animated_data(&self, data: &Self::Data) -> ChartResult<Self::AnimatedData> {
951        // Clone the data series for animation
952        Ok(data.clone())
953    }
954}
955
956/// Builder for animated bar charts
957#[cfg(feature = "animations")]
958#[derive(Debug)]
959pub struct AnimatedBarChartBuilder<C: PixelColor> {
960    base_builder: BarChartBuilder<C>,
961    frame_rate: u32,
962}
963
964#[cfg(feature = "animations")]
965impl<C: PixelColor> AnimatedBarChartBuilder<C>
966where
967    C: From<embedded_graphics::pixelcolor::Rgb565>,
968{
969    /// Create a new animated bar chart builder
970    pub fn new() -> Self {
971        Self {
972            base_builder: BarChartBuilder::new(),
973            frame_rate: 60,
974        }
975    }
976
977    /// Set the target frame rate
978    pub fn frame_rate(mut self, fps: u32) -> Self {
979        self.frame_rate = fps.clamp(1, 120);
980        self
981    }
982
983    /// Set the bar orientation
984    pub fn orientation(mut self, orientation: BarOrientation) -> Self {
985        self.base_builder = self.base_builder.orientation(orientation);
986        self
987    }
988
989    /// Set bar colors
990    pub fn colors(mut self, colors: &[C]) -> Self {
991        self.base_builder = self.base_builder.colors(colors);
992        self
993    }
994
995    /// Set bar width
996    pub fn bar_width(mut self, width: BarWidth) -> Self {
997        self.base_builder = self.base_builder.bar_width(width);
998        self
999    }
1000
1001    /// Set spacing between bars
1002    pub fn spacing(mut self, spacing: u32) -> Self {
1003        self.base_builder = self.base_builder.spacing(spacing);
1004        self
1005    }
1006
1007    /// Add a border to bars
1008    pub fn with_border(mut self, border: BorderStyle<C>) -> Self {
1009        self.base_builder = self.base_builder.with_border(border);
1010        self
1011    }
1012
1013    /// Enable stacked bars
1014    pub fn stacked(mut self, stacked: bool) -> Self {
1015        self.base_builder = self.base_builder.stacked(stacked);
1016        self
1017    }
1018
1019    /// Set the chart title
1020    pub fn with_title(mut self, title: &str) -> Self {
1021        self.base_builder = self.base_builder.with_title(title);
1022        self
1023    }
1024
1025    /// Set the background color
1026    pub fn background_color(mut self, color: C) -> Self {
1027        self.base_builder = self.base_builder.background_color(color);
1028        self
1029    }
1030
1031    /// Build the animated bar chart
1032    pub fn build(self) -> ChartResult<AnimatedBarChart<C>> {
1033        let base_chart = self.base_builder.build()?;
1034
1035        Ok(AnimatedBarChart {
1036            base_chart,
1037            current_data: None,
1038        })
1039    }
1040}
1041
1042#[cfg(feature = "animations")]
1043impl<C: PixelColor> Default for AnimatedBarChartBuilder<C>
1044where
1045    C: From<embedded_graphics::pixelcolor::Rgb565>,
1046{
1047    fn default() -> Self {
1048        Self::new()
1049    }
1050}