use vello_cpu::kurbo::Point;
use crate::{
component::context::{CartesianRenderer, PolarRenderer, SeriesContext},
visual::{Color, Stroke, VisualElement},
};
pub fn render_cartesian_pipeline<R: CartesianRenderer>(
renderer: &R,
ctx: &SeriesContext,
) -> Vec<VisualElement> {
if renderer.is_data_empty() {
return Vec::new();
}
renderer.render_cartesian(ctx)
}
pub fn render_polar_pipeline<R: PolarRenderer>(
renderer: &R,
ctx: &SeriesContext,
) -> Vec<VisualElement> {
if renderer.is_empty() {
return Vec::new();
}
let plot_bounds = ctx.plot_bounds();
let center_percent = renderer.center_percent();
let center = Point::new(
plot_bounds.x0 + plot_bounds.width() * center_percent.0 / 100.0,
plot_bounds.y0 + plot_bounds.height() * center_percent.1 / 100.0,
);
let radius_percent = renderer.radius_percent();
let max_radius = ctx.get_polar_max_radius() * radius_percent.1 / 100.0;
renderer.render_polar(ctx, center, max_radius)
}
#[derive(Debug, Clone)]
pub struct SeriesStyle {
pub color: Color,
pub stroke: Option<Stroke>,
pub fill: Option<Color>,
}
impl SeriesStyle {
pub fn new(color: Color) -> Self {
Self {
color,
stroke: None,
fill: Some(color),
}
}
pub fn with_stroke(mut self, stroke: Stroke) -> Self {
self.stroke = Some(stroke);
self
}
pub fn with_fill(mut self, fill: Color) -> Self {
self.fill = Some(fill);
self
}
pub fn without_stroke(mut self) -> Self {
self.stroke = None;
self
}
pub fn without_fill(mut self) -> Self {
self.fill = None;
self
}
}
#[derive(Debug, Clone)]
pub struct LabelConfig {
pub show: bool,
pub position: LabelPosition,
pub color: Color,
pub font_size: f64,
pub font_family: String,
pub formatter: Option<String>,
}
impl Default for LabelConfig {
fn default() -> Self {
Self {
show: false,
position: LabelPosition::Top,
color: Color::new(0, 0, 0),
font_size: 12.0,
font_family: "sans-serif".to_string(),
formatter: None,
}
}
}
impl LabelConfig {
pub fn enabled(color: Color, font_size: f64) -> Self {
Self {
show: true,
color,
font_size,
..Default::default()
}
}
pub fn with_position(mut self, position: LabelPosition) -> Self {
self.position = position;
self
}
pub fn with_formatter(mut self, formatter: String) -> Self {
self.formatter = Some(formatter);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LabelPosition {
Top,
Inside,
Bottom,
Left,
Right,
Outside,
Center,
}
pub fn create_label_config(label: &Option<crate::model::Label>) -> LabelConfig {
match label {
Some(l) if l.show => LabelConfig {
show: true,
position: match l.position {
crate::model::LabelPosition::Top => LabelPosition::Top,
crate::model::LabelPosition::Inside => LabelPosition::Inside,
crate::model::LabelPosition::Bottom => LabelPosition::Bottom,
crate::model::LabelPosition::Left => LabelPosition::Left,
crate::model::LabelPosition::Right => LabelPosition::Right,
crate::model::LabelPosition::Outside => LabelPosition::Outside,
_ => LabelPosition::Top,
},
color: l.color,
font_size: l.font_size,
font_family: l.font_family.clone(),
formatter: l.formatter.clone(),
},
_ => LabelConfig::default(),
}
}
pub mod grid_utils {
use super::*;
pub fn calc_category_center_x(ctx: &SeriesContext, index: usize) -> f64 {
ctx.coord.x_to_pixel(index as f64 + 0.5)
}
pub fn calc_y_pixel(ctx: &SeriesContext, value: f64, y_axis_index: usize) -> f64 {
ctx.coord.y_to_pixel(value, y_axis_index)
}
pub fn calc_bar_width(ctx: &SeriesContext, ratio: f64) -> f64 {
ctx.category_width() * ratio
}
}
pub mod color_utils {
use super::*;
pub fn apply_opacity(color: Color, opacity: f64) -> Color {
let mut result = color;
result.a = (result.a as f64 * opacity).clamp(0.0, 255.0) as u8;
result
}
pub fn get_contrast_color(background: Color) -> Color {
let luminance = (0.299 * background.r as f64
+ 0.587 * background.g as f64
+ 0.114 * background.b as f64)
/ 255.0;
if luminance > 0.5 {
Color::new(0, 0, 0) } else {
Color::new(255, 255, 255) }
}
}