ruviz 0.4.10

High-performance 2D plotting library for Rust
Documentation
use super::*;
use crate::core::types::Point2f;
use crate::plots::{PlotArea, heatmap::HeatmapData};
use crate::render::{Color, LineStyle, MarkerStyle, skia::SkiaRenderer};
use std::sync::Arc;

pub(super) type ClipRect = (f32, f32, f32, f32);

#[derive(Debug, Clone)]
pub(super) struct PolylineBatch {
    points: Arc<[Point2f]>,
    color: Color,
    line_width: f32,
    line_style: LineStyle,
    clip_rect: ClipRect,
}

impl PolylineBatch {
    pub(super) fn new(
        points: Arc<[Point2f]>,
        color: Color,
        line_width: f32,
        line_style: LineStyle,
        clip_rect: ClipRect,
    ) -> Self {
        Self {
            points,
            color,
            line_width,
            line_style,
            clip_rect,
        }
    }

    fn execute(&self, renderer: &mut SkiaRenderer) -> Result<()> {
        renderer.draw_polyline_points_clipped(
            self.points.as_ref(),
            self.color,
            self.line_width,
            self.line_style.clone(),
            self.clip_rect,
        )
    }
}

#[derive(Debug, Clone)]
pub(super) struct MarkerBatch {
    points: Arc<[Point2f]>,
    size: f32,
    style: MarkerStyle,
    color: Color,
    clip_rect: ClipRect,
}

impl MarkerBatch {
    pub(super) fn new(
        points: Arc<[Point2f]>,
        size: f32,
        style: MarkerStyle,
        color: Color,
        clip_rect: ClipRect,
    ) -> Self {
        Self {
            points,
            size,
            style,
            color,
            clip_rect,
        }
    }

    fn execute(&self, renderer: &mut SkiaRenderer) -> Result<()> {
        renderer.draw_markers_clipped(
            self.points.as_ref(),
            self.size,
            self.style,
            self.color,
            self.clip_rect,
        )
    }
}

#[derive(Debug, Clone)]
pub(super) struct RectGridBatch {
    x_edges: Arc<[i32]>,
    y_edges: Arc<[i32]>,
    colors: Arc<[Option<Color>]>,
    n_rows: usize,
    n_cols: usize,
    cell_borders: bool,
}

impl RectGridBatch {
    pub(super) fn from_heatmap_data(
        data: &HeatmapData,
        area: PlotArea,
        alpha: f32,
    ) -> Option<Self> {
        if !data.can_use_pixel_aligned_grid_fast_path(alpha) {
            return None;
        }

        let (x_edges, y_edges) = data.pixel_aligned_screen_edges(&area);
        let colors = data
            .values
            .iter()
            .flat_map(|row| row.iter())
            .map(|&value| {
                (!data.should_mask_value(value)).then(|| data.get_color(value).with_alpha(alpha))
            })
            .collect::<Vec<_>>();

        Some(Self {
            x_edges: x_edges.into(),
            y_edges: y_edges.into(),
            colors: colors.into(),
            n_rows: data.n_rows,
            n_cols: data.n_cols,
            cell_borders: data.config.cell_borders,
        })
    }

    fn execute(&self, renderer: &mut SkiaRenderer) -> Result<()> {
        for row in 0..self.n_rows {
            let top = self.y_edges[row].min(self.y_edges[row + 1]);
            let bottom = self.y_edges[row].max(self.y_edges[row + 1]);
            if bottom <= top {
                continue;
            }

            for col in 0..self.n_cols {
                let Some(cell_color) = self.colors[row * self.n_cols + col] else {
                    continue;
                };
                let left = self.x_edges[col].min(self.x_edges[col + 1]);
                let right = self.x_edges[col].max(self.x_edges[col + 1]);
                if right <= left {
                    continue;
                }

                let x = left as f32;
                let y = top as f32;
                let width = (right - left) as f32;
                let height = (bottom - top) as f32;
                renderer.draw_pixel_aligned_solid_rectangle(x, y, width, height, cell_color)?;

                if self.cell_borders {
                    renderer.draw_pixel_aligned_rectangle_outline(
                        x,
                        y,
                        width,
                        height,
                        cell_color.darken(0.2),
                    )?;
                }
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone)]
pub(super) enum StaticRasterBatch {
    Polyline(PolylineBatch),
    Markers(MarkerBatch),
    RectGrid(RectGridBatch),
}

impl StaticRasterBatch {
    fn execute(&self, renderer: &mut SkiaRenderer) -> Result<()> {
        match self {
            Self::Polyline(batch) => batch.execute(renderer),
            Self::Markers(batch) => batch.execute(renderer),
            Self::RectGrid(batch) => batch.execute(renderer),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub(super) struct SeriesRasterPlan {
    batches: Vec<StaticRasterBatch>,
    used_exact_line_canonicalization: bool,
    used_raster_line_reduction: bool,
}

impl SeriesRasterPlan {
    pub(super) fn push_polyline(
        &mut self,
        points: Arc<[Point2f]>,
        color: Color,
        line_width: f32,
        line_style: LineStyle,
        clip_rect: ClipRect,
    ) {
        self.batches
            .push(StaticRasterBatch::Polyline(PolylineBatch::new(
                points, color, line_width, line_style, clip_rect,
            )));
    }

    pub(super) fn push_markers(
        &mut self,
        points: Arc<[Point2f]>,
        size: f32,
        style: MarkerStyle,
        color: Color,
        clip_rect: ClipRect,
    ) {
        self.batches
            .push(StaticRasterBatch::Markers(MarkerBatch::new(
                points, size, style, color, clip_rect,
            )));
    }

    pub(super) fn push_rect_grid(&mut self, batch: RectGridBatch) {
        self.batches.push(StaticRasterBatch::RectGrid(batch));
    }

    pub(super) fn note_exact_line_canonicalization(&mut self) {
        self.used_exact_line_canonicalization = true;
    }

    pub(super) fn note_raster_line_reduction(&mut self) {
        self.used_raster_line_reduction = true;
    }

    pub(super) fn execute(&self, renderer: &mut SkiaRenderer) -> Result<()> {
        if self.used_exact_line_canonicalization {
            renderer.note_exact_line_canonicalization();
        }
        if self.used_raster_line_reduction {
            renderer.note_raster_line_reduction();
        }

        for batch in &self.batches {
            batch.execute(renderer)?;
        }
        Ok(())
    }
}

pub(super) fn clip_rect_from_plot_area(plot_area: tiny_skia::Rect) -> ClipRect {
    (
        plot_area.x(),
        plot_area.y(),
        plot_area.width(),
        plot_area.height(),
    )
}

pub(super) fn project_xy_points(
    x_data: &[f64],
    y_data: &[f64],
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
    plot_area: tiny_skia::Rect,
) -> Arc<[Point2f]> {
    x_data
        .iter()
        .zip(y_data.iter())
        .map(|(&x, &y)| {
            let (px, py) = crate::render::skia::map_data_to_pixels(
                x, y, x_min, x_max, y_min, y_max, plot_area,
            );
            Point2f::new(px, py)
        })
        .collect::<Vec<_>>()
        .into()
}

pub(super) fn plot_area_from_rect(
    plot_area: tiny_skia::Rect,
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
) -> PlotArea {
    PlotArea::new(
        plot_area.x(),
        plot_area.y(),
        plot_area.width(),
        plot_area.height(),
        x_min,
        x_max,
        y_min,
        y_max,
    )
}