Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_austenite/src/bin/plot_demo.rs

4.7 KiB, 30 runs

created by r1870400018:36577, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! `plot_demo` -- plots functions as figures and sets them in a real document.
2//!
3//! Builds two plots with the plotting module -- the sine and cosine over two periods, and the standard
4//! normal distribution -- wraps each as a captioned figure, and authors a one-page document around
5//! them. The whole is run through the two-pass driver, decorated with a folio, and written as SVG pages
6//! and a PDF, so the plotted axes, grid, curves and tick labels land on a typeset page as first-class
7//! drawn content.
8//!
9//! Usage: `plot_demo [OUTPUT_DIR]` (default `plot-out`).
10
11use oxedyne_fe2o3_austenite::{
12 doc::{
13 self,
14 Block,
15 },
16 theme::Theme,
17 driver::{
18 self,
19 Config,
20 },
21 emit::Emitter,
22 font::FontMetrics,
23 ir::Graphic,
24 page::PageGeometry,
25 plot::{
26 Plot,
27 Series,
28 },
29};
30
31use oxedyne_fe2o3_core::prelude::*;
32use oxedyne_fe2o3_font::{
33 face::Role,
34 set::FontSet,
35 shape::Dir,
36};
37use oxedyne_fe2o3_graphics::colour::Rgba;
38
39use std::f64::consts::PI;
40use std::sync::Arc;
41
42fn main() -> Outcome<()> {
43 let args: Vec<String> = std::env::args().collect();
44 let out_dir = match args.get(1) {
45 Some(s) => s.clone(),
46 None => "plot-out".to_string(),
47 };
48
49 let fonts = Arc::new(res!(oxedyne_fe2o3_austenite::fonts::libertinus()));
50 let geom = PageGeometry::a4();
51 let style = Theme::default();
52
53 let waves = res!(trig_plot(fonts.clone()));
54 let bell = res!(gaussian_plot(fonts.clone()));
55
56 let blocks = vec![
57 Block::heading(1, "Two Functions, Plotted"),
58 Block::paragraph(
59 "Each figure below is a plot built by Austenite's plotting module: the axes, the grid and \
60 every curve are stroked outline paths, and the tick labels are shaped in the document's own \
61 font, so a plot is drawn geometry a reader can inspect rather than a pasted picture."),
62 Block::figure(waves, Some("The sine and cosine over two periods.".to_string()), None),
63 Block::paragraph(
64 "The standard normal distribution, the bell curve of the central limit theorem, is drawn the \
65 same way from a sampling of its density."),
66 Block::figure(bell, Some("The standard normal density.".to_string()), None),
67 ];
68
69 let (document, heads) = res!(doc::author(fonts.clone(), geom, &style, &oxedyne_fe2o3_austenite::fonts::FaceResolver::default(), &blocks, None, None));
70
71 let metrics = FontMetrics::new(fonts.clone(), Role::Body, Dir::Ltr, style.text.body_size);
72 let mut out = res!(driver::run(&document, &metrics, Config::default()));
73 res!(doc::decorate(&mut out.pages, &out.ledger, &heads, &fonts, &style, geom, "", None));
74
75 res!(std::fs::create_dir_all(&out_dir));
76 let emitter = Emitter::Svg;
77 for page in &out.pages {
78 let svg = res!(emitter.render(page));
79 let path = fmt!("{}/page-{:03}.{}", out_dir, page.number, emitter.extension());
80 res!(std::fs::write(&path, svg));
81 }
82 let pdf = res!(oxedyne_fe2o3_austenite::emit::pdf::render_document(&out.pages));
83 res!(std::fs::write(fmt!("{}/document.pdf", out_dir), pdf));
84
85 println!(
86 "plot_demo: composed {} page(s) in {} pass(es); wrote SVG page(s) and document.pdf to {}/",
87 out.pages.len(), out.passes, out_dir);
88 Ok(())
89}
90
91/// The sine and cosine over two full periods, sampled finely enough to read as smooth curves.
92fn trig_plot(fonts: Arc<FontSet>) -> Outcome<Graphic> {
93 let n = 240;
94 let (a, b) = (-2.0 * PI, 2.0 * PI);
95 let sample = |f: fn(f64) -> f64| -> Vec<(f64, f64)> {
96 (0..=n).map(|k| {
97 let x = a + (b - a) * k as f64 / n as f64;
98 (x, f(x))
99 }).collect()
100 };
101
102 let plot = Plot {
103 width: 380.0,
104 height: 200.0,
105 x_range: (a, b),
106 y_range: (-1.25, 1.25),
107 x_ticks: vec![-6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0],
108 y_ticks: vec![-1.0, -0.5, 0.0, 0.5, 1.0],
109 series: vec![
110 Series { points: sample(f64::sin), colour: Rgba::opaque(30, 90, 200), width: 1.4, dashed: false, label: None },
111 Series { points: sample(f64::cos), colour: Rgba::opaque(200, 60, 50), width: 1.4, dashed: false, label: None },
112 ],
113 axis: oxedyne_fe2o3_austenite::plot::AxisStyle::Framed,
114 x_label: None,
115 y_label: None,
116 legend: false,
117 };
118 plot.build(fonts)
119}
120
121/// The standard normal density, sampled over four standard deviations each side of the mean.
122fn gaussian_plot(fonts: Arc<FontSet>) -> Outcome<Graphic> {
123 let n = 240;
124 let (a, b) = (-4.0, 4.0);
125 let norm = 1.0 / (2.0 * PI).sqrt();
126 let points = (0..=n).map(|k| {
127 let x = a + (b - a) * k as f64 / n as f64;
128 (x, norm * (-0.5 * x * x).exp())
129 }).collect();
130
131 let plot = Plot {
132 width: 380.0,
133 height: 190.0,
134 x_range: (a, b),
135 y_range: (0.0, 0.45),
136 x_ticks: vec![-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0],
137 y_ticks: vec![0.0, 0.1, 0.2, 0.3, 0.4],
138 series: vec![
139 Series { points, colour: Rgba::opaque(120, 40, 160), width: 1.6, dashed: false, label: None },
140 ],
141 axis: oxedyne_fe2o3_austenite::plot::AxisStyle::Framed,
142 x_label: None,
143 y_label: None,
144 legend: false,
145 };
146 plot.build(fonts)
147}