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 | |
| 11 | use 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 | |
| 31 | use oxedyne_fe2o3_core::prelude::*; |
| 32 | use oxedyne_fe2o3_font::{ |
| 33 | face::Role, |
| 34 | set::FontSet, |
| 35 | shape::Dir, |
| 36 | }; |
| 37 | use oxedyne_fe2o3_graphics::colour::Rgba; |
| 38 | |
| 39 | use std::f64::consts::PI; |
| 40 | use std::sync::Arc; |
| 41 | |
| 42 | fn 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. |
| 92 | fn 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. |
| 122 | fn 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 | } |