use vello_cpu::kurbo::Rect;
use super::{
DataCoordinateSystem, GridDefinition, GridManager, GridRect, LayoutContext, Layoutable,
SizeConstraint,
};
use crate::option::AxisPosition;
#[derive(Debug, Clone)]
pub struct AxisArea {
pub axis_bbox: Rect,
pub label_bbox: Rect,
pub position: AxisPosition,
pub grid_bbox: Rect,
}
#[derive(Debug, Clone)]
pub struct GridLayoutInfo {
pub grid_index: usize,
pub grid_bbox: Rect,
pub grid_inner_bbox: Rect,
pub x_axis_areas: Vec<AxisArea>,
pub y_axis_areas: Vec<AxisArea>,
pub data_coord: DataCoordinateSystem,
}
#[derive(Debug, Clone)]
pub struct LayoutOutput {
pub title_bbox: Option<Rect>,
pub legend_bbox: Option<Rect>,
pub grids: Vec<GridLayoutInfo>,
}
pub struct SubplotLayout {
pub grid_index: usize,
pub grid: Box<dyn Layoutable>,
pub x_axes: Vec<Box<dyn Layoutable>>,
pub y_axes: Vec<Box<dyn Layoutable>>,
pub left: crate::model::Position,
pub right: crate::model::Position,
pub top: crate::model::Position,
pub bottom: crate::model::Position,
}
pub struct ChartLayout {
pub title: Option<Box<dyn Layoutable>>,
pub legend: Option<Box<dyn Layoutable>>,
pub subplots: Vec<SubplotLayout>,
}
pub struct LayoutEngine {
context: LayoutContext,
grid_manager: GridManager,
}
impl LayoutEngine {
pub fn new(context: LayoutContext) -> Self {
let grid_manager = GridManager::new(context.clone());
Self {
context,
grid_manager,
}
}
pub fn layout(&mut self, chart_layout: &mut ChartLayout) -> LayoutOutput {
self.measure(chart_layout);
let chart_bounds = Rect::new(
0.0,
0.0,
self.context.chart_width,
self.context.chart_height,
);
let grid_definitions: Vec<GridDefinition> = chart_layout
.subplots
.iter()
.map(|subplot| {
GridDefinition::new(
subplot.grid_index,
crate::model::Grid {
left: subplot.left.clone(),
right: subplot.right.clone(),
top: subplot.top.clone(),
bottom: subplot.bottom.clone(),
contain_label: false,
},
)
})
.collect();
let grid_rects = self
.grid_manager
.compute_layout(&grid_definitions, chart_bounds);
self.arrange_with_grids(chart_layout, chart_bounds, &grid_rects)
}
fn measure(&mut self, chart_layout: &mut ChartLayout) {
if let Some(ref mut title) = chart_layout.title {
let constraint = SizeConstraint {
min_width: 0.0,
max_width: f64::INFINITY,
min_height: 0.0,
max_height: self.context.chart_height * 0.3,
};
title.measure(constraint);
}
if let Some(ref mut legend) = chart_layout.legend {
let constraint = SizeConstraint {
min_width: 0.0,
max_width: f64::INFINITY,
min_height: 0.0,
max_height: self.context.chart_height * 0.2,
};
legend.measure(constraint);
}
for subplot in &mut chart_layout.subplots {
for axis in &mut subplot.x_axes {
let constraint = SizeConstraint::vertical_unlimited(self.context.chart_width);
axis.measure(constraint);
}
for axis in &mut subplot.y_axes {
let constraint = SizeConstraint::horizontal_unlimited(self.context.chart_height);
axis.measure(constraint);
}
let constraint = SizeConstraint::unlimited();
subplot.grid.measure(constraint);
}
}
fn arrange_with_grids(
&mut self,
chart_layout: &mut ChartLayout,
chart_bounds: Rect,
grid_rects: &[GridRect],
) -> LayoutOutput {
let mut current_y = chart_bounds.y0 + self.context.padding;
let mut has_title = false;
if let Some(ref mut title) = chart_layout.title
&& let Some(result) = title.layout_result()
{
let title_height = result.desired_size.height;
let title_bounds = Rect::new(
chart_bounds.x0 + self.context.padding,
current_y,
chart_bounds.x1 - self.context.padding,
current_y + title_height,
);
title.arrange(title_bounds);
current_y += title_height;
has_title = true;
}
let mut has_legend = false;
if let Some(ref mut legend) = chart_layout.legend
&& let Some(result) = legend.layout_result()
{
let height = result.desired_size.height;
if has_title {
current_y += self.context.spacing;
}
let legend_bounds = Rect::new(
chart_bounds.x0 + self.context.padding,
current_y,
chart_bounds.x1 - self.context.padding,
current_y + height,
);
legend.arrange(legend_bounds);
current_y += height;
has_legend = true;
}
if has_title || has_legend {
current_y += self.context.spacing;
}
let mut grids = Vec::new();
for (subplot, grid_rect) in chart_layout.subplots.iter_mut().zip(grid_rects.iter()) {
let grid_info = self.arrange_subplot_with_grid(subplot, grid_rect, current_y);
grids.push(grid_info);
}
LayoutOutput {
title_bbox: chart_layout
.title
.as_ref()
.and_then(|t| t.layout_result().map(|r| r.bounds)),
legend_bbox: chart_layout
.legend
.as_ref()
.and_then(|l| l.layout_result().map(|r| r.bounds)),
grids,
}
}
fn arrange_subplot_with_grid(
&mut self,
subplot: &mut SubplotLayout,
grid_rect: &GridRect,
plot_top: f64,
) -> GridLayoutInfo {
let mut grid_bounds = grid_rect.bounds;
if grid_bounds.y0 < plot_top {
let offset = plot_top - grid_bounds.y0;
grid_bounds = Rect::new(
grid_bounds.x0,
plot_top,
grid_bounds.x1,
grid_bounds.y1 + offset,
);
}
let inner_bounds = grid_bounds;
let mut current_left_width = 0.0;
let mut current_right_width = 0.0;
let mut y_axis_areas = Vec::new();
for (i, axis) in subplot.y_axes.iter_mut().enumerate() {
if let Some(result) = axis.layout_result() {
let axis_width = result.desired_size.width;
let is_right = i % 2 == 1;
let (axis_bounds, position) = if is_right {
let bounds = Rect::new(
grid_bounds.x1 + current_right_width,
grid_bounds.y0,
grid_bounds.x1 + current_right_width + axis_width,
grid_bounds.y1,
);
current_right_width += axis_width;
(bounds, AxisPosition::Right)
} else {
let bounds = Rect::new(
grid_bounds.x0 - axis_width - current_left_width,
grid_bounds.y0,
grid_bounds.x0 - current_left_width,
grid_bounds.y1,
);
current_left_width += axis_width;
(bounds, AxisPosition::Left)
};
axis.arrange(axis_bounds);
y_axis_areas.push(AxisArea {
axis_bbox: axis_bounds,
label_bbox: axis_bounds,
position,
grid_bbox: inner_bounds,
});
}
}
let mut current_bottom_height = 0.0;
let mut current_top_height = 0.0;
let mut x_axis_areas = Vec::new();
for (i, axis) in subplot.x_axes.iter_mut().enumerate() {
if let Some(result) = axis.layout_result() {
let axis_height = result.desired_size.height;
let is_top = i % 2 == 1;
let (axis_bounds, position) = if is_top {
let bounds = Rect::new(
grid_bounds.x0,
grid_bounds.y0 - axis_height - current_top_height,
grid_bounds.x1,
grid_bounds.y0 - current_top_height,
);
current_top_height += axis_height;
(bounds, AxisPosition::Top)
} else {
let bounds = Rect::new(
grid_bounds.x0,
grid_bounds.y1 + current_bottom_height,
grid_bounds.x1,
grid_bounds.y1 + current_bottom_height + axis_height,
);
current_bottom_height += axis_height;
(bounds, AxisPosition::Bottom)
};
axis.arrange(axis_bounds);
x_axis_areas.push(AxisArea {
axis_bbox: axis_bounds,
label_bbox: axis_bounds,
position,
grid_bbox: inner_bounds,
});
}
}
subplot.grid.arrange(inner_bounds);
GridLayoutInfo {
grid_index: subplot.grid_index,
grid_bbox: grid_bounds,
grid_inner_bbox: inner_bounds,
x_axis_areas,
y_axis_areas,
data_coord: DataCoordinateSystem::default(),
}
}
}