use crate::chart_data;
use crate::chart_modal::Mark;
use crate::render::context::RenderContext;
use crate::widgets::{
self,
chart::{ChartRenderData, ChartView, PlotNumbers},
ui::Working,
};
use crate::{ChartPrepared, ChartRequest};
use ratatui::layout::Rect;
use ratatui::widgets::{Clear, Widget};
pub fn render(
chart_area: Rect,
buf: &mut ratatui::buffer::Buffer,
app: &mut crate::App,
ctx: &RenderContext,
) {
Clear.render(chart_area, buf);
app.chart_modal.units = app
.data_table_state
.as_ref()
.map(|state| state.units())
.unwrap_or_default();
let request = ChartRequest::from_modal(&app.chart_modal);
let outcome = request
.as_ref()
.and_then(|request| app.chart_cache.get(request));
let error = outcome.and_then(|o| o.as_ref().err()).map(String::as_str);
let computing = request.is_some() && outcome.is_none();
let prepared = match &request {
Some(request) if computing => app.chart_cache.standing_in(request),
_ => outcome.and_then(|o| o.as_ref().ok()),
};
let notes = prepared
.map(|p| app.chart_notes_of(p, crate::glyphs::get().middot))
.unwrap_or_default();
let aggregating = request.as_ref().is_some_and(ChartRequest::aggregates);
let schema = app
.data_table_state
.as_ref()
.map(|state| state.schema().as_ref());
let column = |name: &str| chart_data::AxisNumbers::column(&ctx.number_format, schema, name);
let columns =
|names: &[String]| chart_data::AxisNumbers::columns(&ctx.number_format, schema, names);
let modal = &app.chart_modal;
let spec = modal.effective_spec();
let x_name = spec.encoding.x.field.clone();
let y_numbers = || {
use crate::chart_modal::Aggregate;
match spec.encoding.y.aggregate {
Aggregate::Count | Aggregate::Distinct => {
chart_data::AxisNumbers::count(&ctx.number_format)
}
a if a.is_fractional() => columns(&spec.encoding.y.field).fractional(),
_ => columns(&spec.encoding.y.field),
}
};
let xy_numbers = || PlotNumbers {
x: x_name.as_deref().map(column).unwrap_or_default(),
y: y_numbers(),
};
const NO_NAMES: &[String] = &[];
let render_data = match (modal.mark(), prepared) {
(Mark::Line | Mark::Scatter, Some(ChartPrepared::XY(c))) => ChartRenderData::XY {
series: if modal.log_scale {
c.series_log.as_ref().or(Some(&c.series))
} else {
Some(&c.series)
},
breaks: Some(&c.breaks),
values: Some(&c.series),
names: &c.names,
x_axis_kind: c.x_axis_kind,
x_bounds: None,
other: c.other,
numbers: xy_numbers(),
},
(Mark::Line | Mark::Scatter, Some(ChartPrepared::XRange(c))) => ChartRenderData::XY {
series: None,
breaks: None,
values: None,
names: NO_NAMES,
x_axis_kind: c.x_axis_kind,
x_bounds: Some((c.x_min, c.x_max)),
other: false,
numbers: xy_numbers(),
},
(Mark::Line | Mark::Scatter, _) => {
let x_axis_kind = match (x_name.as_deref(), schema) {
(Some(x), Some(schema)) => chart_data::x_axis_temporal_kind_for_column(schema, x),
_ => chart_data::XAxisTemporalKind::Numeric,
};
ChartRenderData::XY {
series: None,
breaks: None,
values: None,
names: NO_NAMES,
x_axis_kind,
x_bounds: None,
other: false,
numbers: xy_numbers(),
}
}
(Mark::Histogram, prepared) => {
let data = match prepared {
Some(ChartPrepared::Histogram(d)) => Some(d),
_ => None,
};
ChartRenderData::Histogram {
data,
x: data.map(|d| column(&d.column)).unwrap_or_default(),
}
}
(Mark::Box, prepared) => {
let data = match prepared {
Some(ChartPrepared::BoxPlot(d)) => Some(d),
_ => None,
};
ChartRenderData::BoxPlot {
data,
y: columns(&spec.encoding.y.field),
}
}
(Mark::Kde, prepared) => {
let data = match prepared {
Some(ChartPrepared::Kde(d)) => Some(d),
_ => None,
};
ChartRenderData::Kde {
data,
x: x_name.as_deref().map(column).unwrap_or_default(),
}
}
(Mark::Heatmap, prepared) => {
let data = match prepared {
Some(ChartPrepared::Heatmap(d)) => Some(d),
_ => None,
};
ChartRenderData::Heatmap {
data,
numbers: PlotNumbers {
x: data.map(|d| column(&d.x_column)).unwrap_or_default(),
y: data.map(|d| column(&d.y_column)).unwrap_or_default(),
},
}
}
(Mark::Bar, prepared) => ChartRenderData::Bar {
data: match prepared {
Some(ChartPrepared::Bar(d)) => Some(d),
_ => None,
},
},
};
let grouping;
let working_text = if aggregating {
grouping = app.chart_status();
grouping.as_str()
} else {
"Computing chart..."
};
widgets::chart::render_chart_view(
chart_area,
buf,
&mut app.chart_modal,
&app.theme,
ctx,
ChartView {
data: render_data,
notes,
error,
working: computing.then_some(Working {
text: working_text,
frame: app.throbber_frame as usize,
}),
schema,
},
);
if app.chart_export_modal.active {
let modal_width = (chart_area.width * 3 / 4).min(66);
let modal_height =
widgets::chart_export_modal::height(&app.chart_export_modal).min(chart_area.height);
let modal_x = chart_area.x + chart_area.width.saturating_sub(modal_width) / 2;
let modal_y = chart_area.y + chart_area.height.saturating_sub(modal_height) / 2;
let modal_area = Rect {
x: modal_x,
y: modal_y,
width: modal_width,
height: modal_height,
};
widgets::chart_export_modal::render_chart_export_modal(
modal_area,
buf,
&mut app.chart_export_modal,
ctx,
);
}
}