use hephaestus::backend::vello::VelloRenderer;
use hephaestus::color::{rgb8, Color};
use hephaestus::composition::{beside, Composition, Patch};
use hephaestus::geometry::Size;
use hephaestus::plot::chrome::axis::{Axis, AxisPlacement, PolarRing};
use hephaestus::plot::projection::{PolarProjection, Projection};
use hephaestus::plot::{scale, Plot, PlotComposition, PointGeom, RectGeom, SegmentGeom};
use hephaestus::scene::SceneBuilder;
use hephaestus::Renderer;
fn comp_shape() -> Composition {
beside(
beside(
beside(Patch::new("scatter"), Patch::new("rose")),
Patch::new("gauge"),
),
Patch::new("partial"),
)
}
fn partial_projection() -> Projection {
Projection::Polar(
PolarProjection::full_circle()
.theta_range(
-std::f64::consts::PI / 3.0,
3.0 * std::f64::consts::PI / 4.0,
)
.inner_radius(0.2),
)
}
fn main() {
let (w, h) = (2000u32, 500u32);
let dpi = 96.0;
let scatter_n = 60;
let scatter_theta: Vec<f64> = (0..scatter_n)
.map(|i| (i as f64 / scatter_n as f64) * std::f64::consts::TAU)
.collect();
let scatter_radius: Vec<f64> = (0..scatter_n)
.map(|i| {
let t = (i as f64 * 0.137).sin() * 0.5 + 0.5;
0.2 + 0.8 * t
})
.collect();
let dot = rgb8(40, 100, 200);
let rose_n = 12;
let rose_step = std::f64::consts::TAU / rose_n as f64;
let rose_theta_a: Vec<f64> = (0..rose_n).map(|i| (i as f64 - 0.4) * rose_step).collect();
let rose_theta_b: Vec<f64> = (0..rose_n).map(|i| (i as f64 + 0.4) * rose_step).collect();
let rose_inner: Vec<f64> = (0..rose_n).map(|_| 0.1_f64).collect();
let rose_outer: Vec<f64> = (0..rose_n)
.map(|i| {
let t = (i as f64 / rose_n as f64) * std::f64::consts::TAU;
0.4 + 0.4 * (t * 2.0).cos().abs()
})
.collect();
let bar_color = rgb8(180, 80, 100);
let needle_theta = vec![0.7_f64];
let needle_inner = vec![0.0_f64];
let needle_outer = vec![1.0_f64];
let needle_color = rgb8(60, 30, 30);
let partial_n = 40;
let partial_theta: Vec<f64> = (0..partial_n)
.map(|i| i as f64 / (partial_n - 1) as f64)
.collect();
let partial_radius: Vec<f64> = (0..partial_n)
.map(|i| {
let t = i as f64 / (partial_n - 1) as f64;
0.3 + 0.6 * t })
.collect();
let partial_dot = rgb8(120, 60, 160);
let mut view = PlotComposition::new(&comp_shape())
.add_scale("theta_full", scale::continuous(0.0..=std::f64::consts::TAU))
.add_scale("radius_unit", scale::continuous(0.0..=1.0))
.add_scale("gauge_theta", scale::continuous(0.0..=1.0))
.add_scale("gauge_radius", scale::continuous(0.0..=1.0))
.add_scale("partial_theta", scale::continuous(0.0..=1.0))
.add_scale("partial_radius", scale::continuous(0.0..=1.0));
let mut p_scatter = Plot::new(&comp_shape(), "scatter")
.projection(Projection::polar())
.bind("x", "theta_full")
.bind("y", "radius_unit");
p_scatter.add_geom(
PointGeom::builder()
.set("x", scatter_theta.clone())
.set("y", scatter_radius.clone())
.set("fill", dot)
.set("size", 6.0_f64)
.build(),
);
p_scatter.add_axis(
Axis::rail("theta_full", AxisPlacement::PolarAngular(PolarRing::Outer)).title("Theta"),
);
p_scatter.add_axis(
Axis::rail(
"radius_unit",
AxisPlacement::PolarRadius { theta_frac: 0.0 },
)
.title("Radius"),
);
view.attach_plot(p_scatter);
let mut p_rose = Plot::new(&comp_shape(), "rose")
.projection(Projection::polar())
.bind("x", "theta_full")
.bind("y", "radius_unit");
p_rose.add_geom(
RectGeom::builder()
.set("x", rose_theta_a.clone())
.set("x2", rose_theta_b.clone())
.set("y", rose_inner.clone())
.set("y2", rose_outer.clone())
.set("fill", bar_color)
.build(),
);
p_rose.add_axis(Axis::rail(
"theta_full",
AxisPlacement::PolarAngular(PolarRing::Outer),
));
p_rose.add_axis(Axis::rail(
"radius_unit",
AxisPlacement::PolarRadius { theta_frac: 0.0 },
));
view.attach_plot(p_rose);
let mut p_gauge = Plot::new(&comp_shape(), "gauge")
.projection(Projection::gauge())
.bind("x", "gauge_theta")
.bind("y", "gauge_radius");
p_gauge.add_geom(
SegmentGeom::builder()
.set("x", needle_theta.clone())
.set("x2", needle_theta)
.set("y", needle_inner)
.set("y2", needle_outer)
.set("stroke", needle_color)
.set("linewidth", 4.0_f64)
.build(),
);
p_gauge.add_axis(Axis::rail(
"gauge_theta",
AxisPlacement::PolarAngular(PolarRing::Outer),
));
p_gauge.add_axis(Axis::rail(
"gauge_theta",
AxisPlacement::PolarAngular(PolarRing::Inner),
));
p_gauge.add_axis(Axis::rail(
"gauge_radius",
AxisPlacement::PolarRadius { theta_frac: 0.0 },
));
view.attach_plot(p_gauge);
let mut p_partial = Plot::new(&comp_shape(), "partial")
.projection(partial_projection())
.bind("x", "partial_theta")
.bind("y", "partial_radius");
p_partial.add_geom(
PointGeom::builder()
.set("x", partial_theta.clone())
.set("y", partial_radius.clone())
.set("fill", partial_dot)
.set("size", 7.0_f64)
.build(),
);
p_partial.add_axis(Axis::rail(
"partial_theta",
AxisPlacement::PolarAngular(PolarRing::Outer),
));
p_partial.add_axis(Axis::rail(
"partial_radius",
AxisPlacement::PolarRadius { theta_frac: 0.0 },
));
view.attach_plot(p_partial);
let issues = view.validate();
if !issues.is_empty() {
panic!("validate() reported issues: {issues:?}");
}
let mut renderer = VelloRenderer::new().expect("vello renderer init");
let bg: Color = rgb8(248, 248, 252);
{
let scene = renderer.scene();
scene.clear();
view.render(scene, Size::new(w as f64, h as f64), dpi);
}
let mut pixels = vec![0u8; (w * h * 4) as usize];
renderer
.render_to_buffer(w, h, bg, &mut pixels)
.expect("render");
let path = std::env::current_dir().unwrap().join("examples/polar.png");
hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
println!("wrote {}", path.display());
}