#[doc(no_inline)]
pub use super::*;
pub type Figure<'a, 'b> = FigureBase<'a, 'b, FourBar, [f64; 2]>;
pub fn history<B, R, H>(root: R, history: H) -> PResult<(), B>
where
B: DrawingBackend,
Canvas<B>: From<R>,
H: AsRef<[f64]>,
{
history_pareto(root, history, [])
}
pub fn history_pareto<B, R, H, P>(root: R, history: H, pareto: P) -> PResult<(), B>
where
B: DrawingBackend,
Canvas<B>: From<R>,
H: AsRef<[f64]>,
P: AsRef<[usize]>,
{
const FONT_SIZE: i32 = 24;
let font = ("Times New Roman", FONT_SIZE).into_font().color(&BLACK);
let history = history.as_ref();
let pareto = pareto.as_ref();
let root = Canvas::from(root);
root.fill(&WHITE)?;
let max_fitness = history
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
let mut chart = ChartBuilder::on(&root);
chart
.set_label_area_size(LabelAreaPosition::Left, (10).percent())
.set_label_area_size(LabelAreaPosition::Bottom, (10).percent());
if !pareto.is_empty() {
chart.right_y_label_area_size((8).percent());
}
let mut chart = chart
.margin((4).percent())
.build_cartesian_2d(0..history.len() - 1, 0.0..*max_fitness)?;
macro_rules! impl_history {
($chart:ident) => {
$chart
.configure_mesh()
.disable_x_mesh()
.disable_y_mesh()
.x_desc("Generation")
.x_label_style(font.clone())
.y_desc("Fitness")
.y_label_style(font.clone())
.draw()?;
$chart
.draw_series(LineSeries::new(history.iter().copied().enumerate(), BLUE))?
.label("Best Fitness")
.legend(|c| {
EmptyElement::at(c) + PathElement::new([(1, 0), (FONT_SIZE - 1, 0)], BLUE)
});
};
}
if pareto.is_empty() {
impl_history!(chart);
} else {
let max_pareto = pareto.iter().max().unwrap();
let mut chart = chart.set_secondary_coord(0..history.len() - 1, 0..max_pareto + 1);
impl_history!(chart);
chart
.configure_secondary_axes()
.label_style(font.clone())
.y_desc("Pareto Size")
.axis_desc_style(font.clone())
.draw()?;
chart
.draw_secondary_series(LineSeries::new(pareto.iter().copied().enumerate(), RED))?
.label("Pareto Size")
.legend(|c| EmptyElement::at(c) + PathElement::new([(1, 0), (FONT_SIZE - 1, 0)], RED));
chart
.configure_series_labels()
.legend_area_size(FONT_SIZE)
.position(SeriesLabelPosition::UpperLeft)
.background_style(WHITE)
.border_style(BLACK)
.label_font(font)
.draw()?;
}
root.present()
}
pub fn pareto<B, R, P>(root: R, pareto: P) -> PResult<(), B>
where
B: DrawingBackend,
Canvas<B>: From<R>,
P: AsRef<[crate::syn::MOFit]>,
{
let font = ("Times New Roman", 24).into_font().color(&BLACK);
let pareto = pareto.as_ref();
let root = Canvas::from(root);
root.fill(&WHITE)?;
let [x_max, y_max, z_max] = pareto.iter().fold([0.; 3], |edge, ys| {
[
ys.curve.max(edge[0]),
ys.pose.max(edge[1]),
ys.center.max(edge[2]),
]
});
sfb::xyz_label(&root, 24., ["y₃", "y₂", "y₁"])?;
let mut chart = ChartBuilder::on(&root)
.set_label_area_size(LabelAreaPosition::Left, (8).percent())
.set_label_area_size(LabelAreaPosition::Bottom, (4).percent())
.margin((2).percent())
.margin_left((15).percent())
.build_cartesian_3d(0.0..x_max, 0.0..y_max, 0.0..z_max)?;
chart.with_projection(|mut pb| {
pb.yaw = -std::f64::consts::FRAC_PI_4 * 3.;
pb.scale = 0.9;
pb.into_matrix()
});
chart
.configure_axes()
.max_light_lines(0)
.light_grid_style(sfb::LIGHTGRAY)
.label_style(font)
.axis_panel_style(TRANSPARENT)
.x_labels(4)
.z_labels(4)
.x_formatter(&formatter)
.y_formatter(&formatter)
.z_formatter(&formatter)
.draw()?;
chart.draw_series(
pareto
.iter()
.map(|ys| Circle::new((ys.curve, ys.pose, ys.center), 3, RED)),
)?;
root.present()
}
impl Plot for Figure<'_, '_> {
fn plot_by<B>(&self, root: &Canvas<B>, t: Option<f64>) -> PResult<(), B>
where
B: DrawingBackend,
{
self.check_empty::<B>()?;
root.fill(&WHITE)?;
let (stroke, dot_size) = self.get_dot_size();
let joints = t
.and_then(|t| self.get_joints(t))
.or_else(|| self.get_joints_auto(Into::into));
let Opt { grid, axis, legend, .. } = self.opt;
let [x_spec, y_spec] = {
use mech::CurveGen as _;
let joints = joints.into_iter().flatten().collect();
let possible_p = (self.fb.as_deref())
.filter(|_| t.is_some())
.into_iter()
.flat_map(|fb| fb.curves(8))
.flatten()
.collect();
let iter = self.lines().map(|data| data.line.boundary());
area2d(iter.chain([joints, possible_p]), root.dim_in_pixel())
};
let mut chart = ChartBuilder::on(root)
.set_label_area_size(LabelAreaPosition::Left, (8).percent())
.set_label_area_size(LabelAreaPosition::Bottom, (4).percent())
.margin((4).percent())
.build_cartesian_2d(x_spec, y_spec)?;
let mut mesh = chart.configure_mesh();
if !grid {
mesh.disable_mesh();
}
if !axis {
mesh.disable_axes();
}
mesh.label_style(self.get_font())
.x_label_formatter(&formatter)
.y_label_formatter(&formatter)
.draw()?;
for data in self.lines() {
data.draw(&mut chart, stroke, self.font)?;
}
if let Some(joints @ [p1, p2, p3, p4, p5]) = joints {
for line in [[p1, p3].as_slice(), &[p3, p5, p4, p3], &[p2, p4]] {
let line = line.iter().map(|&[x, y]| (x, y));
chart.draw_series(LineSeries::new(line, BLACK.stroke_width(stroke)))?;
}
let grounded = joints[..2].iter().map(|&[x, y]| {
EmptyElement::at((x, y))
+ TriangleMarker::new((0, 10), dot_size + 3, BLACK.filled())
});
chart.draw_series(grounded)?;
let joints = joints.iter().enumerate().map(|(n, &[x, y])| {
let t_style = self.get_big_font().color(&BLUE);
EmptyElement::at((x, y))
+ Circle::new((0, 0), dot_size, BLACK.filled())
+ Text::new(format!("p{}", Subscript(n + 1)), (5, 5), t_style)
});
chart.draw_series(joints)?;
}
if let Some(legend) = legend.to_plotter_pos().filter(|_| self.has_legend()) {
chart
.configure_series_labels()
.legend_area_size(self.font)
.position(legend)
.background_style(WHITE)
.border_style(BLACK)
.label_font(self.get_font())
.draw()?;
}
root.present()
}
}
pub fn area2d<I>(pts: I, area: (u32, u32)) -> [std::ops::Range<f64>; 2]
where
I: IntoIterator,
ExtBound<2>: FromIterator<I::Item>,
{
let [w, h] = [area.0 as f64, area.1 as f64];
let [[x_min, x_max], [y_min, y_max]] = ExtBound::from_iter(pts).map_to(|min, max| [min, max]);
let dx = (x_max - x_min).abs();
let dy = (y_max - y_min).abs();
let x_cen = (x_min + x_max) * 0.5;
let y_cen = (y_min + y_max) * 0.5;
match (dx > dy, w > h, dx / dy < w / h) {
(true, true, false) | (false, false, false) | (true, false, _) => {
let x_r = dx * 0.5 * 1.2;
let y_r = dx / w * h * 0.5 * 1.2;
[x_cen - x_r..x_cen + x_r, y_cen - y_r..y_cen + y_r]
}
(true, true, true) | (false, false, true) | (false, true, _) => {
let y_r = dy * 0.5 * 1.2;
let x_r = dy / h * w * 0.5 * 1.2;
[x_cen - x_r..x_cen + x_r, y_cen - y_r..y_cen + y_r]
}
}
}