1use hephaestus::backend::vello::VelloRenderer;
34use hephaestus::color::{rgb8, Color};
35use hephaestus::composition::{beside, grid, Composition, Element, Patch};
36use hephaestus::geometry::Size;
37use hephaestus::plot::chrome::axis::{Axis, AxisPlacement};
38use hephaestus::plot::chrome::legend::{Legend, LegendKeySpec};
39use hephaestus::plot::{scale, Plot, PlotComposition, PointGeom};
40use hephaestus::scales::chrome::{AxisSide, LegendSide};
41use hephaestus::scales::value::Value;
42use hephaestus::scene::SceneBuilder;
43use hephaestus::Renderer;
44
45fn comp_shape(aspect: Option<(f64, f64)>) -> Composition {
46 let facets: Vec<Element> = ["q1", "q2", "q3", "q4"]
47 .into_iter()
48 .map(|id| Patch::new(id).into())
49 .collect();
50 let inner_2x2 = grid(2, 2, facets);
51 let outer = beside(inner_2x2, Patch::new("summary"));
52 match aspect {
53 Some((aw, ah)) => outer.aspect(aw, ah),
54 None => outer,
55 }
56}
57
58fn main() {
59 let (w, h) = (1400u32, 700u32);
60 let dpi = 96.0;
61
62 let xs: Vec<f64> = (0..50).map(|i| i as f64 * 2.0).collect();
63 let make = |phase: f64, amp: f64| -> Vec<f64> {
64 xs.iter()
65 .map(|x| 50.0 + amp * (x * 0.05 + phase).sin())
66 .collect()
67 };
68
69 let datasets = [
70 ("q1", make(0.0, 25.0), rgb8(220, 90, 70)),
71 ("q2", make(1.5, 18.0), rgb8(70, 120, 220)),
72 ("q3", make(3.0, 22.0), rgb8(70, 180, 120)),
73 ("q4", make(4.5, 28.0), rgb8(180, 130, 80)),
74 ("summary", make(0.0, 35.0), rgb8(130, 80, 180)),
75 ];
76
77 let mut renderer = VelloRenderer::new().expect("vello renderer init");
78 let bg: Color = rgb8(248, 248, 252);
79
80 {
82 #[allow(unused_mut)]
83 let mut view = PlotComposition::new(&comp_shape(None))
84 .add_scale("time", scale::continuous(0.0..=100.0))
85 .add_scale("y", scale::continuous(0.0..=100.0))
86 .title("Sensor array")
87 .subtitle("Four quadrants and a summary, sharing one time scale")
88 .caption("Composition-level chrome spans every panel");
89 attach_all(&mut view, &xs, &datasets);
90
91 let mut view = {
96 let series: Vec<Value> = datasets.iter().map(|(id, _, _)| Value::from(*id)).collect();
97 let colors: Vec<Color> = datasets.iter().map(|(_, _, c)| *c).collect();
98 let mut view = view
99 .add_scale("series", scale::discrete(series).range_colors(colors))
100 .axis_title(AxisSide::Bottom, "Time (s)");
101 view.add_legend(
102 Legend::new("series")
103 .side(LegendSide::Right)
104 .title("Series")
105 .key(
106 LegendKeySpec::point()
107 .scaled("fill", "series")
108 .fixed("size", 6.0_f64),
109 ),
110 );
111 view
112 };
113
114 let issues = view.validate();
115 if !issues.is_empty() {
116 panic!("validate() reported issues: {issues:?}");
117 }
118
119 render_to(
120 &mut renderer,
121 &mut view,
122 w,
123 h,
124 dpi,
125 bg,
126 "examples/faceted_1_initial.png",
127 );
128
129 view.update_scale("time", |s| s.set_domain_continuous(20.0, 60.0));
130 render_to(
131 &mut renderer,
132 &mut view,
133 w,
134 h,
135 dpi,
136 bg,
137 "examples/faceted_2_shared_zoom.png",
138 );
139 }
140
141 {
150 let (lw, lh) = (1800u32, 600u32);
151 let mut view = PlotComposition::new(&comp_shape(Some((1.0, 1.0))))
152 .add_scale("time", scale::continuous(20.0..=60.0))
153 .add_scale("y", scale::continuous(0.0..=100.0));
154 attach_all(&mut view, &xs, &datasets);
155 render_to(
156 &mut renderer,
157 &mut view,
158 lw,
159 lh,
160 dpi,
161 bg,
162 "examples/faceted_3_aspect_locked.png",
163 );
164 }
165}
166
167fn attach_all(view: &mut PlotComposition, xs: &[f64], datasets: &[(&str, Vec<f64>, Color)]) {
168 let shape = comp_shape(None);
173 for (id, ys, color) in datasets {
174 let mut p = Plot::new(&shape, *id).bind("x", "time").bind("y", "y");
175 p.add_geom(
176 PointGeom::builder()
177 .set("x", xs.to_vec())
178 .set("y", ys.clone())
179 .set("fill", *color)
180 .set("size", 4.0_f64)
181 .build(),
182 );
183 {
184 p.add_axis(Axis::rail(
185 "time",
186 AxisPlacement::Cartesian(AxisSide::Bottom),
187 ));
188 p.add_axis(Axis::rail("y", AxisPlacement::Cartesian(AxisSide::Left)));
189 }
190 view.attach_plot(p);
191 }
192}
193
194fn render_to(
195 renderer: &mut VelloRenderer,
196 view: &mut PlotComposition,
197 w: u32,
198 h: u32,
199 dpi: f64,
200 bg: Color,
201 out_relative: &str,
202) {
203 {
204 let scene = renderer.scene();
205 scene.clear();
206 view.render(scene, Size::new(w as f64, h as f64), dpi);
207 }
208 let mut pixels = vec![0u8; (w * h * 4) as usize];
209 renderer
210 .render_to_buffer(w, h, bg, &mut pixels)
211 .expect("render");
212 let path = std::env::current_dir().unwrap().join(out_relative);
213 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
214 println!("wrote {}", path.display());
215}