use crate::data::ChartData;
use crate::error::Result;
use crate::layers::{Layer, LayerStage};
use crate::renderer::RenderContext;
use crate::style::ChartStyle;
use crate::theme::{ChartTheme, Color};
use crate::viewport::Viewport;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineSeriesConfig {
pub values: Vec<f64>,
pub label: String,
pub color: [f32; 4],
pub line_width: f32,
pub opacity: f32,
}
impl Default for LineSeriesConfig {
fn default() -> Self {
Self {
values: Vec::new(),
label: "Indicator".to_string(),
color: [0.13, 0.59, 0.95, 1.0], line_width: 1.5,
opacity: 1.0,
}
}
}
pub struct LineSeriesLayer {
enabled: bool,
needs_render: bool,
config: LineSeriesConfig,
points: Vec<[f32; 2]>,
}
impl LineSeriesLayer {
pub fn new(config: LineSeriesConfig) -> Self {
Self {
enabled: true,
needs_render: true,
config,
points: Vec::new(),
}
}
pub fn with_values(values: Vec<f64>, label: impl Into<String>, color: Color) -> Self {
Self::new(LineSeriesConfig {
values,
label: label.into(),
color: color.to_array(),
line_width: 1.5,
opacity: 1.0,
})
}
pub fn set_values(&mut self, values: Vec<f64>) {
self.config.values = values;
self.needs_render = true;
}
pub fn set_color(&mut self, color: Color) {
self.config.color = color.to_array();
self.needs_render = true;
}
fn resolve_color(&self, _theme: &ChartTheme) -> Color {
let c = self.config.color;
Color::new(c[0], c[1], c[2], c[3] * self.config.opacity)
}
}
impl Layer for LineSeriesLayer {
fn name(&self) -> &str {
&self.config.label
}
fn stage(&self) -> LayerStage {
LayerStage::ChartIndicator
}
fn update(
&mut self,
data: &ChartData,
viewport: &Viewport,
_theme: &ChartTheme,
_style: &ChartStyle,
) {
self.points.clear();
if self.config.values.is_empty() {
self.needs_render = false;
return;
}
let (start_idx, end_idx) = match data.visible_indices() {
Some((start, end)) => (start, end),
None => {
self.needs_render = false;
return;
}
};
if start_idx >= end_idx {
self.needs_render = false;
return;
}
let chart_rect = viewport.chart_content_rect();
let bounds = &viewport.chart_bounds;
let duration = bounds.time_duration().num_seconds() as f32;
if duration <= 0.0 {
self.needs_render = false;
return;
}
let time_scale = chart_rect.width / duration;
let time_start = bounds.time_start.timestamp() as f32;
for (idx, i) in (start_idx..end_idx).enumerate() {
if idx >= self.config.values.len() {
break;
}
let value = self.config.values[idx];
if value.is_nan() || value.is_infinite() {
continue;
}
let timestamp = data.main_series.get_x(i);
let delta = timestamp as f32 - time_start;
let x = chart_rect.x + delta * time_scale;
let y = viewport.chart_to_screen_y(value as f32);
self.points.push([x, y]);
}
self.needs_render = self.points.len() > 1;
}
fn render(
&self,
context: &mut RenderContext,
_render_pass: &mut wgpu::RenderPass,
) -> Result<()> {
if !self.enabled || self.points.len() < 2 {
return Ok(());
}
let theme = context.theme().clone();
let color = self.resolve_color(&theme);
for window in self.points.windows(2) {
let start = window[0];
let end = window[1];
context.draw_line(start, end, color, self.config.line_width);
}
Ok(())
}
fn needs_render(&self) -> bool {
self.needs_render
}
fn z_order(&self) -> i32 {
51 }
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
self.needs_render = true;
}
}