1use std::sync::Arc;
24
25use hephaestus::backend::vello::VelloRenderer;
26use hephaestus::color::{rgb, rgb8, Color};
27use hephaestus::composition::{Composition, Patch, Span};
28use hephaestus::geometry::Size;
29use hephaestus::plot::chrome::axis::{Axis, AxisPlacement};
30use hephaestus::plot::chrome::legend::{Legend, LegendKeySpec};
31use hephaestus::plot::geom::linetype::{dashed, dotted, solid};
32use hephaestus::plot::{scale, Plot, PlotComposition, PointGeom};
33use hephaestus::scales::chrome::{Anchor, AxisSide, LegendSide};
34use hephaestus::scales::value::Value;
35use hephaestus::scene::SceneBuilder;
36use hephaestus::shape::ShapeRegistry;
37use hephaestus::text::{glyph_marker, TextStyle};
38use hephaestus::Renderer;
39
40fn comp() -> Composition {
41 Composition::empty(1, 1).place(1, 1, Span::cell(), Patch::new("panel"))
42}
43
44fn main() {
45 let (w, h) = (900u32, 600u32);
46 let dpi = 96.0;
47
48 let n = 24;
50 let xs: Vec<f64> = (0..n).map(|i| i as f64 * 0.4).collect();
51 let ys: Vec<f64> = (0..n)
52 .map(|i| ((i as f64) * 0.5).sin() * 0.4 + 0.5)
53 .collect();
54 let cats: [&'static str; 4] = ["A", "B", "C", "D"];
55 let fill_col: Vec<&'static str> = (0..n).map(|i| cats[i % 4]).collect();
56 let size_col: Vec<&'static str> = (0..n).map(|i| cats[i % 4]).collect();
57
58 let glyph_style = TextStyle::new(16.0);
64 let mut shapes = ShapeRegistry::with_builtins();
65 shapes.insert("droplet", glyph_marker("\u{1F4A7}", &glyph_style));
69 shapes.insert("herb", glyph_marker("\u{1F33F}", &glyph_style));
70 shapes.insert("sun", glyph_marker("\u{1F31E}", &glyph_style));
71 shapes.insert("wave", glyph_marker("\u{1F30A}", &glyph_style));
72 shapes.insert("fire", glyph_marker("\u{1F525}", &glyph_style));
73
74 let mut p = Plot::new(&comp(), "panel")
75 .bind("x", "x")
76 .bind("y", "y")
77 .bind("fill", "category_color")
78 .bind("size", "category_size")
79 .shape_registry(shapes);
80 p.add_geom(
81 PointGeom::builder()
82 .set("x", xs)
83 .set("y", ys)
84 .set("fill", fill_col)
85 .set("size", size_col)
86 .build(),
87 );
88 p.add_axis(Axis::rail("x", AxisPlacement::Cartesian(AxisSide::Bottom)));
89 p.add_axis(Axis::rail("y", AxisPlacement::Cartesian(AxisSide::Left)));
90
91 p.add_legend(
96 Legend::new("category_color")
97 .side(LegendSide::Right)
98 .title("Category")
99 .key(LegendKeySpec::line().scaled("stroke", "category_color")),
100 );
101 p.add_legend(
102 Legend::new("category_color")
103 .side(LegendSide::Right)
104 .title("Category")
105 .key(
106 LegendKeySpec::point()
107 .scaled("fill", "category_color")
108 .fixed("stroke", Value::Color(rgb(0.0, 0.0, 0.0)))
109 .fixed("size", 6.0_f64),
110 ),
111 );
112
113 p.add_legend(
119 Legend::new("category_size")
120 .side(LegendSide::Right)
121 .title("Size")
122 .key(
123 LegendKeySpec::point()
124 .scaled("size", "category_size")
125 .fixed("fill", Value::Color(rgb(0.25, 0.25, 0.25))),
126 ),
127 );
128
129 p.add_legend(
135 Legend::colorbar("gradient_scale")
136 .side(LegendSide::Right)
137 .title("Gradient")
138 .scaled("fill_opacity", "gradient_opacity"),
139 );
140
141 p.add_legend(
148 Legend::colorbar("gradient_scale")
149 .side(LegendSide::Bottom)
150 .title("Steps")
151 .binned()
152 .open_upper(),
153 );
154
155 p.add_legend(
168 Legend::new("gradient_scale")
169 .side(LegendSide::Bottom)
170 .title("Bins")
171 .binned()
172 .equal_bins()
173 .key(
174 LegendKeySpec::point()
175 .scaled("shape", "bin_shape")
176 .fixed("fill", Value::Color(rgb(0.15, 0.15, 0.15)))
177 .fixed("size", 12.0_f64),
178 ),
179 );
180
181 p.add_legend(
186 Legend::new("category_size")
187 .side(LegendSide::Left)
188 .title("Font size")
189 .key(
190 LegendKeySpec::text()
191 .scaled("size", "category_size")
192 .scaled("fill", "category_color")
193 .fixed("text", Value::String(Arc::from("Aa"))),
194 ),
195 );
196
197 p.add_legend(
199 Legend::new("category_line")
200 .side(LegendSide::Bottom)
201 .title("Pattern")
202 .key(
203 LegendKeySpec::line()
204 .scaled("linetype", "category_line")
205 .fixed("linewidth", 1.5_f64),
206 ),
207 );
208 p.add_legend(
209 Legend::new("category_color")
210 .side(LegendSide::Bottom)
211 .title("Colour")
212 .key(LegendKeySpec::rect().scaled("fill", "category_color")),
213 );
214
215 p.add_legend(
219 Legend::new("category_color")
220 .side(LegendSide::InPanel {
221 anchor: Anchor::TopRight,
222 inset_pt: 8.0,
223 })
224 .title("Overlay")
225 .key(
226 LegendKeySpec::point()
227 .scaled("fill", "category_color")
228 .fixed("size", 6.0_f64),
229 ),
230 );
231
232 let cat_values: Vec<Value> = cats.iter().map(|s| Value::String(Arc::from(*s))).collect();
233 let line_cats: [&'static str; 3] = ["Solid", "Dashed", "Dotted"];
234 let line_values: Vec<Value> = line_cats
235 .iter()
236 .map(|s| Value::String(Arc::from(*s)))
237 .collect();
238
239 let mut view = PlotComposition::new(&comp())
240 .add_scale("x", scale::continuous(0.0..=10.0))
241 .add_scale("y", scale::continuous(0.0..=1.0))
242 .add_scale(
243 "category_color",
244 scale::discrete(cat_values.clone()).range_colors([
245 rgb8(220, 90, 70),
246 rgb8(70, 160, 90),
247 rgb8(70, 120, 220),
248 rgb8(180, 120, 200),
249 ]),
250 )
251 .add_scale(
252 "category_size",
253 scale::discrete(cat_values).range_numbers([4.0, 8.0, 12.0, 16.0]),
254 )
255 .add_scale(
256 "category_line",
257 scale::discrete(line_values).range_linetypes([solid(), dashed(), dotted()]),
258 )
259 .add_scale(
260 "gradient_scale",
261 scale::continuous(0.0..=100.0).range_colors([
262 rgb8(20, 30, 90),
263 rgb8(60, 160, 200),
264 rgb8(230, 220, 100),
265 rgb8(220, 60, 40),
266 ]),
267 )
268 .add_scale(
269 "gradient_opacity",
270 scale::continuous(0.0..=100.0).range_numbers([0.1, 1.0]),
271 )
272 .add_scale(
273 "bin_shape",
274 scale::continuous(0.0..=100.0).range_strings([
275 Arc::from("droplet"),
276 Arc::from("herb"),
277 Arc::from("sun"),
278 Arc::from("wave"),
279 Arc::from("fire"),
280 ]),
281 );
282 view.attach_plot(p);
283
284 let issues = view.validate();
285 if !issues.is_empty() {
286 panic!("validate(): {issues:?}");
287 }
288
289 let mut renderer = VelloRenderer::new().expect("vello renderer init");
290 let bg: Color = rgb8(252, 252, 252);
291 {
292 let scene = renderer.scene();
293 scene.clear();
294 view.render(scene, Size::new(w as f64, h as f64), dpi);
295 }
296 let mut pixels = vec![0u8; (w * h * 4) as usize];
297 renderer
298 .render_to_buffer(w, h, bg, &mut pixels)
299 .expect("render");
300 let path = std::env::current_dir()
301 .unwrap()
302 .join("examples/legends.png");
303 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
304 println!("wrote {}", path.display());
305}