use super::types::{StreamBaseline, StreamLayer, StreamOrder};
use crate::style::Color;
use crate::widget::traits::WidgetProps;
#[derive(Debug, Clone)]
pub struct Streamline {
pub title: Option<String>,
pub layers: Vec<StreamLayer>,
pub baseline: StreamBaseline,
pub order: StreamOrder,
pub show_legend: bool,
pub show_labels: bool,
pub x_labels: Vec<String>,
pub bg_color: Option<Color>,
pub height: Option<u16>,
pub palette: Vec<Color>,
pub highlighted: Option<usize>,
pub props: WidgetProps,
}
impl Default for Streamline {
fn default() -> Self {
Self::new()
}
}
impl Streamline {
pub fn new() -> Self {
Self {
title: None,
layers: Vec::new(),
baseline: StreamBaseline::Symmetric,
order: StreamOrder::None,
show_legend: true,
show_labels: true,
x_labels: Vec::new(),
bg_color: None,
height: None,
palette: vec![
Color::rgb(66, 133, 244), Color::rgb(234, 67, 53), Color::rgb(251, 188, 5), Color::rgb(52, 168, 83), Color::rgb(155, 89, 182), Color::rgb(241, 196, 15), Color::rgb(26, 188, 156), Color::rgb(230, 126, 34), Color::rgb(149, 165, 166), Color::rgb(231, 76, 60), ],
highlighted: None,
props: WidgetProps::new(),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn layer(mut self, layer: StreamLayer) -> Self {
self.layers.push(layer);
self
}
pub fn layers(mut self, layers: impl IntoIterator<Item = StreamLayer>) -> Self {
self.layers.extend(layers);
self
}
pub fn baseline(mut self, mode: StreamBaseline) -> Self {
self.baseline = mode;
self
}
pub fn order(mut self, order: StreamOrder) -> Self {
self.order = order;
self
}
pub fn show_legend(mut self, show: bool) -> Self {
self.show_legend = show;
self
}
pub fn show_labels(mut self, show: bool) -> Self {
self.show_labels = show;
self
}
pub fn x_labels(mut self, labels: Vec<String>) -> Self {
self.x_labels = labels;
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg_color = Some(color);
self
}
pub fn height(mut self, height: u16) -> Self {
self.height = Some(height);
self
}
pub fn palette(mut self, colors: Vec<Color>) -> Self {
self.palette = colors;
self
}
pub fn highlight(mut self, index: usize) -> Self {
self.highlighted = Some(index);
self
}
pub fn get_layer_color(&self, index: usize) -> Color {
if index < self.layers.len() {
self.layers[index]
.color
.unwrap_or_else(|| self.palette[index % self.palette.len()])
} else {
self.palette[index % self.palette.len()]
}
}
pub fn compute_stacks(&self) -> Vec<Vec<(f64, f64)>> {
if self.layers.is_empty() {
return Vec::new();
}
let num_points = self
.layers
.iter()
.map(|l| l.values.len())
.max()
.unwrap_or(0);
if num_points == 0 {
return Vec::new();
}
let ordered_indices: Vec<usize> = match self.order {
StreamOrder::None => (0..self.layers.len()).collect(),
StreamOrder::Ascending => {
let mut indices: Vec<_> = (0..self.layers.len()).collect();
indices.sort_by(|&a, &b| {
let sum_a: f64 = self.layers[a].values.iter().sum();
let sum_b: f64 = self.layers[b].values.iter().sum();
sum_a
.partial_cmp(&sum_b)
.unwrap_or(std::cmp::Ordering::Equal)
});
indices
}
StreamOrder::Descending => {
let mut indices: Vec<_> = (0..self.layers.len()).collect();
indices.sort_by(|&a, &b| {
let sum_a: f64 = self.layers[a].values.iter().sum();
let sum_b: f64 = self.layers[b].values.iter().sum();
sum_b
.partial_cmp(&sum_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
indices
}
StreamOrder::InsideOut => {
let mut indices: Vec<_> = (0..self.layers.len()).collect();
indices.sort_by(|&a, &b| {
let sum_a: f64 = self.layers[a].values.iter().sum();
let sum_b: f64 = self.layers[b].values.iter().sum();
sum_b
.partial_cmp(&sum_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut result = Vec::with_capacity(indices.len());
for (i, idx) in indices.into_iter().enumerate() {
if i % 2 == 0 {
result.push(idx);
} else {
result.insert(0, idx);
}
}
result
}
};
let mut stacks: Vec<Vec<(f64, f64)>> = vec![Vec::new(); self.layers.len()];
for x in 0..num_points {
let values: Vec<f64> = ordered_indices
.iter()
.map(|&i| self.layers[i].values.get(x).copied().unwrap_or(0.0))
.collect();
let total: f64 = values.iter().sum();
let (y0, scale) = match self.baseline {
StreamBaseline::Zero => (0.0, 1.0),
StreamBaseline::Symmetric => (-total / 2.0, 1.0),
StreamBaseline::Wiggle => {
let n = self.layers.len() as f64;
let offset: f64 = values
.iter()
.enumerate()
.map(|(i, &v)| (n - i as f64) * v)
.sum::<f64>()
/ n;
(-offset / 2.0, 1.0)
}
StreamBaseline::Expand => {
let scale = if total > 0.0 { 1.0 / total } else { 1.0 };
(0.0, scale)
}
};
let mut y = y0;
for (i, &orig_idx) in ordered_indices.iter().enumerate() {
let value = values[i] * scale;
let y_start = y;
let y_end = y + value;
stacks[orig_idx].push((y_start, y_end));
y = y_end;
}
}
stacks
}
}