liecharts 0.1.0-beta

A Rust charting library with PNG and SVG rendering support
Documentation
use crate::component::{ChartComponent, SeriesComponent, SeriesContext};
use crate::layout::LayoutOutput;
use crate::model::{PieSeries, ResolvedOption};
use crate::pipeline::mapper::{CoordinateMapper, PolarPieMapper};
use crate::pipeline::transform::{DataTransformer, IdentityTransformer};
use crate::pipeline::builder::VisualBuilder;
use crate::visual::{Color, VisualElement};

pub struct PieSeriesComponent {
    series: PieSeries,
    series_index: usize,
    grid_index: usize,
}

impl PieSeriesComponent {
    pub fn new(series: &PieSeries, series_index: usize) -> Self {
        Self {
            series: series.clone(),
            series_index,
            grid_index: series.grid_index,
        }
    }

    fn build_with_context(&self, ctx: &SeriesContext) -> Vec<VisualElement> {
        let plot_bounds = ctx.plot_bounds();

        let coord = crate::layout::DataCoordinateSystem::new(
            plot_bounds,
            (0.0, 100.0),
            vec![(0.0, 100.0)],
            false,
            false,
            0,
        );

        let resolved_series = crate::model::ResolvedSeries::Pie(self.series.clone());
        let transformer = IdentityTransformer;
        let transformed_list = transformer.transform(&[resolved_series]);
        let transformed = &transformed_list[0];

        let mapper = PolarPieMapper::new()
            .with_center(self.series.center.0, self.series.center.1)
            .with_radius(self.series.radius.0, self.series.radius.1);
        let mapped = mapper.map(transformed, &coord, 0);

        let label_config = if let Some(label) = &self.series.label {
            if label.show {
                crate::pipeline::LabelConfig {
                    show: true,
                    position: crate::pipeline::LabelPosition::Outside,
                    color: label.color,
                    font_size: label.font_size,
                    font_family: label.font_family.clone(),
                    formatter: None,
                }
            } else {
                crate::pipeline::LabelConfig::default()
            }
        } else {
            crate::pipeline::LabelConfig::default()
        };

        let colors: Vec<Color> = self.series.data.iter()
            .enumerate()
            .map(|(i, _)| {
                ctx.resolved.colors.get(i % ctx.resolved.colors.len())
                    .copied()
                    .unwrap_or(Color::new(0, 0, 0))
            })
            .collect();

        let builder = crate::pipeline::builder::PieVisualBuilder::new()
            .with_label_config(label_config)
            .with_border(Color::new(255, 255, 255), 1.0)
            .with_colors(colors.clone());

        builder.build(transformed, &mapped, &coord)
    }
}

impl SeriesComponent for PieSeriesComponent {
    fn series_index(&self) -> usize {
        self.series_index
    }

    fn grid_index(&self) -> usize {
        self.grid_index
    }

    fn is_empty(&self) -> bool {
        self.series.data.is_empty()
    }
}

impl ChartComponent for PieSeriesComponent {
    fn build_visual_elements(&self, resolved: &ResolvedOption, layout: &LayoutOutput) -> Vec<VisualElement> {
        let ctx = match self.create_context(resolved, layout) {
            Some(ctx) => ctx,
            None => return Vec::new(),
        };
        self.build_with_context(&ctx)
    }
}