Skip to main content

rust_ppm/plot/
mod.rs

1//! Axes, canvas rendering, and line/scatter convenience functions.
2
3mod axes;
4#[path = "plot.rs"]
5mod builder;
6mod canvas;
7mod raster;
8mod style;
9mod text;
10
11pub use axes::Axes;
12pub use builder::{DEFAULT_SIZE, Plot};
13pub use canvas::Canvas;
14pub use style::{LineStyle, MarkerStyle};
15
16use crate::Image;
17
18/// Creates a scatter-plot image from a point list and explicit axis limits.
19pub fn scatter_plot(
20    points: &[(f64, f64)],
21    xlim: (f64, f64),
22    ylim: (f64, f64),
23    width: usize,
24    height: usize,
25) -> Image {
26    scatter_plot_with_size(points, xlim, ylim, width, height, 1)
27}
28
29/// Creates a scatter-plot image with a configurable marker size.
30pub fn scatter_plot_with_size(
31    points: &[(f64, f64)],
32    xlim: (f64, f64),
33    ylim: (f64, f64),
34    width: usize,
35    height: usize,
36    size: usize,
37) -> Image {
38    scatter_plot_with_size_and_inset(
39        points,
40        xlim,
41        ylim,
42        width,
43        height,
44        size,
45        default_inset(width),
46        default_inset(height),
47    )
48}
49
50/// Creates a scatter-plot with explicit pixel insets and marker size.
51#[allow(clippy::too_many_arguments)]
52pub fn scatter_plot_with_size_and_inset(
53    points: &[(f64, f64)],
54    xlim: (f64, f64),
55    ylim: (f64, f64),
56    width: usize,
57    height: usize,
58    size: usize,
59    inset_x: usize,
60    inset_y: usize,
61) -> Image {
62    let mut canvas = Canvas::with_inset(
63        width,
64        height,
65        Axes::from_limits(xlim, ylim),
66        inset_x,
67        inset_y,
68    );
69    canvas.render();
70    canvas.scatter(points, size);
71    canvas.into_image()
72}
73
74/// Creates a line-plot image from a point list and explicit axis limits.
75pub fn line_plot(
76    points: &[(f64, f64)],
77    xlim: (f64, f64),
78    ylim: (f64, f64),
79    width: usize,
80    height: usize,
81) -> Image {
82    line_plot_with_width(points, xlim, ylim, width, height, 1)
83}
84
85/// Creates a line plot with a configurable stroke width.
86pub fn line_plot_with_width(
87    points: &[(f64, f64)],
88    xlim: (f64, f64),
89    ylim: (f64, f64),
90    width: usize,
91    height: usize,
92    line_width: usize,
93) -> Image {
94    line_plot_with_width_and_inset(
95        points,
96        xlim,
97        ylim,
98        width,
99        height,
100        line_width,
101        default_inset(width),
102        default_inset(height),
103    )
104}
105
106/// Creates a line plot with explicit pixel insets and stroke width.
107#[allow(clippy::too_many_arguments)]
108pub fn line_plot_with_width_and_inset(
109    points: &[(f64, f64)],
110    xlim: (f64, f64),
111    ylim: (f64, f64),
112    width: usize,
113    height: usize,
114    line_width: usize,
115    inset_x: usize,
116    inset_y: usize,
117) -> Image {
118    let mut canvas = Canvas::with_inset(
119        width,
120        height,
121        Axes::from_limits(xlim, ylim),
122        inset_x,
123        inset_y,
124    );
125    canvas.render();
126    canvas.plot(points, line_width);
127    canvas.into_image()
128}
129
130fn default_inset(size: usize) -> usize {
131    size / 16
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::Pixel;
138
139    #[test]
140    fn convenience_plots_respect_insets() {
141        let scatter = scatter_plot_with_size_and_inset(
142            &[(0.0, 0.0)],
143            (0.0, 1.0),
144            (0.0, 1.0),
145            11,
146            11,
147            1,
148            2,
149            3,
150        );
151        assert_eq!(scatter.get_pixel(2, 7), Some(&Pixel::rgb(255, 0, 0)));
152        assert_eq!(scatter.get_pixel(0, 10), Some(&Pixel::WHITE));
153
154        let line = line_plot_with_width_and_inset(
155            &[(0.0, 0.0), (1.0, 1.0)],
156            (0.0, 1.0),
157            (0.0, 1.0),
158            11,
159            11,
160            1,
161            2,
162            3,
163        );
164        assert_eq!(line.get_pixel(5, 5), Some(&Pixel::rgb(0, 0, 255)));
165        assert_eq!(line.get_pixel(0, 10), Some(&Pixel::WHITE));
166    }
167
168    #[test]
169    fn line_width_and_marker_size_are_configurable() {
170        let line = line_plot_with_width(&[(0.0, 0.5), (1.0, 0.5)], (0.0, 1.0), (0.0, 1.0), 9, 9, 3);
171        for y in 3..=5 {
172            assert_eq!(line.get_pixel(4, y), Some(&Pixel::rgb(0, 0, 255)));
173        }
174
175        let scatter = scatter_plot_with_size(&[(0.5, 0.5)], (0.0, 1.0), (0.0, 1.0), 5, 5, 3);
176        assert_eq!(scatter.get_pixel(2, 2), Some(&Pixel::rgb(255, 0, 0)));
177    }
178
179    #[test]
180    fn axes_fall_back_to_center_when_zero_is_outside_limits() {
181        let image = scatter_plot(&[], (1.0, 2.0), (1.0, 2.0), 7, 7);
182        assert_eq!(image.get_pixel(3, 3), Some(&Pixel::BLACK));
183    }
184}