Skip to main content

rich_text_chrome/
rich_text_chrome.rs

1//! Demonstrates the `markdown` opt-in on chrome `TextElement`s.
2//!
3//! Setting `markdown = Some(true)` once on the theme's root `text`
4//! element reaches every text slot: the title band, axis titles, the
5//! legend title, and the break labels on both the axis and the legend.
6//!
7//! Renders one plot whose title highlights a variable name in a
8//! hex-colour span, whose subtitle italicises the metric name, whose
9//! caption emphasises the sample size in bold, and whose legend
10//! labels each carry their own inline styling.
11//!
12//! Writes `examples/rich_text_chrome.png`.
13
14use hephaestus::backend::vello::VelloRenderer;
15use hephaestus::color::{rgb8, Color};
16use hephaestus::composition::{Composition, Patch, Span};
17use hephaestus::geometry::Size;
18use hephaestus::plot::chrome::axis::{Axis, AxisPlacement};
19use hephaestus::plot::chrome::legend::{Legend, LegendKeySpec};
20use hephaestus::plot::theme::Theme;
21use hephaestus::plot::{scale, Plot, PlotComposition, PointGeom};
22use hephaestus::scales::value::Value;
23use std::sync::Arc;
24
25use hephaestus::scales::chrome::AxisSide;
26use hephaestus::scene::SceneBuilder;
27use hephaestus::Renderer;
28
29/// A theme where every text slot opts into markdown shaping. One
30/// field on the root element — the cascade carries it to the title
31/// band, the axis titles and labels, and the legend.
32fn markdown_chrome_theme() -> Theme {
33    let mut theme = Theme::default();
34    theme.text.markdown = Some(true);
35    theme
36}
37
38fn main() {
39    let (w, h) = (900u32, 560u32);
40    let dpi = 96.0;
41    let bg: Color = rgb8(250, 250, 253);
42
43    let comp = || Composition::empty(1, 1).place(1, 1, Span::cell(), Patch::new("p"));
44
45    let n = 60;
46    let xs: Vec<f64> = (0..n).map(|i| i as f64 / (n - 1) as f64 * 10.0).collect();
47    let ys: Vec<f64> = xs
48        .iter()
49        .map(|x| 2.5 + 1.2 * (x * 0.7).sin() + 0.4 * (x * 2.1).cos())
50        .collect();
51
52    // Break labels are data-derived, so a category that spells
53    // markdown gets parsed like any other string.
54    let bands: [&'static str; 3] = ["*low*", "**mid**", "{.red high}"];
55    let groups: Vec<&'static str> = ys
56        .iter()
57        .map(|y| match *y {
58            v if v < 2.0 => bands[0],
59            v if v < 3.0 => bands[1],
60            _ => bands[2],
61        })
62        .collect();
63
64    let mut plot = Plot::new(&comp(), "p")
65        .bind("x", "x")
66        .bind("y", "y")
67        .bind("fill", "band")
68        .title("Trend of **{#c14b4b price}** across the day")
69        .subtitle("Metric: *closing_bid* — sampled hourly")
70        .caption("n = **60**, source: {.gray internal}");
71    plot.add_geom(
72        PointGeom::builder()
73            .set("x", xs)
74            .set("y", ys)
75            .set("fill", groups)
76            .set("size", 8.0_f64)
77            .build(),
78    );
79    plot.add_legend(
80        Legend::new("band")
81            .title("**band** of the *close*")
82            .key(LegendKeySpec::point().scaled("fill", "band")),
83    );
84    plot.add_axis(
85        Axis::rail("x", AxisPlacement::Cartesian(AxisSide::Bottom))
86            .title("hour of day, {.gray *UTC*}"),
87    );
88    plot.add_axis(
89        Axis::rail("y", AxisPlacement::Cartesian(AxisSide::Left)).title("**price** (USD)"),
90    );
91
92    let mut view = PlotComposition::new(&comp())
93        .theme(markdown_chrome_theme())
94        .add_scale("x", scale::continuous(0.0..=10.0))
95        .add_scale("y", scale::continuous(0.0..=5.0))
96        .add_scale(
97            "band",
98            scale::discrete(bands.iter().map(|b| Value::String(Arc::from(*b)))).range_colors([
99                rgb8(88, 106, 195),
100                rgb8(214, 146, 60),
101                rgb8(193, 75, 75),
102            ]),
103        )
104        .with_plot(plot);
105
106    let mut renderer = VelloRenderer::new().expect("vello renderer init");
107    {
108        let scene = renderer.scene();
109        scene.clear();
110        view.render(scene, Size::new(w as f64, h as f64), dpi);
111    }
112    let mut pixels = vec![0u8; (w * h * 4) as usize];
113    renderer
114        .render_to_buffer(w, h, bg, &mut pixels)
115        .expect("render");
116    let path = std::env::current_dir()
117        .unwrap()
118        .join("examples/rich_text_chrome.png");
119    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
120    println!("wrote {}", path.display());
121}