use super::{PLOT_MARGIN, TITLE_LINE, count, fit_text};
use crate::{
error::ChartWarning,
metrics::TextMetrics,
scene::{Element, Text, TextAnchor},
spec::ChartSpec,
};
const TITLE_SIZE: f64 = 22.0;
const SMALL_TITLE_SIZE: f64 = 18.0;
pub(crate) const NARROW: u32 = 480;
fn title_size(spec: &ChartSpec) -> f64 {
if spec.width < NARROW {
SMALL_TITLE_SIZE
} else {
TITLE_SIZE
}
}
const TITLE_BLOCK: f64 = 44.0;
pub(crate) fn two_lines<'a>(
text: &'a str,
max_width: f64,
font_size: f64,
metrics: &impl TextMetrics,
) -> Option<(&'a str, &'a str)> {
if metrics.width(text, font_size) <= max_width {
return None;
}
text.match_indices(' ')
.map(|(index, _)| (text[..index].trim_end(), text[index + 1..].trim_start()))
.filter(|(first, rest)| !first.is_empty() && !rest.is_empty())
.take_while(|(first, _)| metrics.width(first, font_size) <= max_width)
.last()
}
pub(crate) fn title_extra(spec: &ChartSpec, max_width: f64, metrics: &impl TextMetrics) -> f64 {
if !spec.show_title {
-TITLE_BLOCK
} else if two_lines(&spec.title, max_width, title_size(spec), metrics).is_some() {
TITLE_LINE
} else {
0.0
}
}
pub(crate) const CONTENT_LEFT: f64 = 16.0;
pub(crate) fn horizontal_title(
spec: &ChartSpec,
(left, plot_width): (f64, f64),
metrics: &impl TextMetrics,
) -> (f64, f64) {
if title_fits(spec, plot_width, metrics) {
(left, plot_width)
} else {
(
CONTENT_LEFT,
f64::from(spec.width) - CONTENT_LEFT - f64::from(PLOT_MARGIN),
)
}
}
pub(crate) fn title_fits(spec: &ChartSpec, max_width: f64, metrics: &impl TextMetrics) -> bool {
let title = spec.title.as_str();
let size = title_size(spec);
metrics.width(title, size) <= max_width
|| two_lines(title, max_width, size, metrics)
.is_some_and(|(_, rest)| metrics.width(rest, size) <= max_width)
}
pub(crate) fn push_title(
elements: &mut Vec<Element>,
spec: &ChartSpec,
x: f64,
max_width: f64,
metrics: &impl TextMetrics,
warnings: &mut Vec<ChartWarning>,
) {
if !spec.show_title {
return;
}
let title = spec.title.as_str();
let size = title_size(spec);
let lines = match two_lines(title, max_width, size, metrics) {
Some((first, rest)) => vec![
first.to_owned(),
fit_text(rest, max_width, size, metrics, warnings, "/title"),
],
None => vec![fit_text(
title, max_width, size, metrics, warnings, "/title",
)],
};
for (index, content) in lines.into_iter().enumerate() {
elements.push(Element::Text(Text {
x,
y: 30.0 + count(index) * TITLE_LINE,
class: if spec.width < NARROW {
"chartlet-title chartlet-title-small"
} else {
"chartlet-title"
},
anchor: TextAnchor::Start,
content,
}));
}
}