cas_graph/graph/opts.rs
1use super::point::{CanvasPoint, GraphPoint};
2
3/// Options to use when drawing a graph.
4#[derive(Clone, Copy, Debug)]
5pub struct GraphOptions {
6 /// The width and height of the canvas, in pixels.
7 ///
8 /// The default value is `(1000, 1000)`.
9 pub canvas_size: CanvasPoint<u16>,
10
11 /// The `(x, y)` point at which to center the graph.
12 ///
13 /// For example, to place the origin at the center of the output image, set this to `(0.0,
14 /// 0.0)`.
15 ///
16 /// This field will automatically be computed when calling
17 /// [`Graph::center_on_points`](super::Graph::center_on_points).
18 ///
19 /// The default value is `(0.0, 0.0)`.
20 pub center: GraphPoint<f64>,
21
22 /// The `(x, y)` scale of the graph.
23 ///
24 /// The scale indicates the distance, in graph units, from the center of the canvas to the edge
25 /// of the canvas. For example, when the graph is centered at `(0.0, 0.0)` with a scale of
26 /// `(10.0, 10.0)`, the visible graph will be from `(x, y): (-10.0, -10.0)` to `(x, y): (10.0,
27 /// 10.0)`.
28 ///
29 /// This field will automatically be computed when calling
30 /// [`Graph::center_on_points`](super::Graph::center_on_points).
31 ///
32 /// The default value is `(10.0, 10.0)`.
33 pub scale: GraphPoint<f64>,
34
35 /// When calling [`Graph::center_on_points`](super::Graph::center_on_points), determines
36 /// whether to scale the x- and y-axes together (resulting in a square graph) or independently
37 /// (resulting in a rectangular graph).
38 ///
39 /// The default value is `false`.
40 pub square_scale: bool,
41
42 /// Whether to label the canvas boundaries with their corresponding graph values.
43 ///
44 /// The default value is `false`.
45 pub label_canvas_boundaries: bool,
46
47 /// The number of graph units between each major grid line, given as a pair of `(x, y)` units.
48 ///
49 /// For example, to have a major grid line every `3.0` units on the x-axis and every `2.0`
50 /// units on the y-axis, set this to `(3.0, 2.0)`.
51 ///
52 /// This field will automatically be computed when calling
53 /// [`Graph::center_on_points`](super::Graph::center_on_points).
54 ///
55 /// The default value is `(2.0, 2.0)`.
56 pub major_grid_spacing: GraphPoint<f64>,
57
58 /// The number of spaces to divide each major grid line into, given as a pair of `(x, y)`
59 /// units. The number of minor grid lines between each major grid line will then be `x - 1` on
60 /// the x-axis, and `y - 1` on the y-axis.
61 ///
62 /// For example, to divide each major grid line into `5` spaces on the x-axis (4 minor grid
63 /// lines) and `4` spaces on the y-axis (3 minor grid lines), set this to `(5, 4)`.
64 ///
65 /// This field will automatically be computed when calling
66 /// [`Graph::center_on_points`](super::Graph::center_on_points).
67 ///
68 /// The default value is `(4, 4)`.
69 pub major_grid_divisions: (u8, u8),
70
71 /// The opacity of the major grid lines, given as a value in the range `0.0` to `1.0`, where
72 /// `0.0` is fully transparent and `1.0` is fully opaque. This also affects the opacity of the
73 /// major grid line numbers.
74 ///
75 /// The default value is `0.8`.
76 pub major_grid_opacity: f64,
77
78 /// The opacity of the minor grid lines, given as a value in the range `0.0` to `1.0`, where
79 /// `0.0` is fully transparent and `1.0` is fully opaque.
80 ///
81 /// The default value is `0.5`.
82 pub minor_grid_opacity: f64,
83}
84
85/// The default options for a graph. Returns a [`GraphOptions`] with the following values:
86///
87/// - [`canvas_size`](GraphOptions::canvas_size): `(1000, 1000)`
88/// - [`center`](GraphOptions::center): `(0.0, 0.0)`
89/// - [`scale`](GraphOptions::scale): `(10.0, 10.0)`
90/// - [`square_scale`](GraphOptions::square_scale): `false`
91/// - [`label_canvas_boundaries`](GraphOptions::label_canvas_boundaries): `false`
92/// - [`major_grid_spacing`](GraphOptions::major_grid_spacing): `(2.0, 2.0)`
93/// - [`major_grid_divisions`](GraphOptions::major_grid_divisions): `(4, 4)`
94impl Default for GraphOptions {
95 fn default() -> GraphOptions {
96 GraphOptions {
97 canvas_size: CanvasPoint(1000, 1000),
98 center: GraphPoint(0.0, 0.0),
99 scale: GraphPoint(10.0, 10.0),
100 square_scale: false,
101 label_canvas_boundaries: false,
102 major_grid_spacing: GraphPoint(2.0, 2.0),
103 major_grid_divisions: (4, 4),
104 major_grid_opacity: 0.8,
105 minor_grid_opacity: 0.4,
106 }
107 }
108}
109
110impl GraphOptions {
111 /// Set the canvas size. Returns an updated [`GraphOptions`] for chaining.
112 pub fn canvas_size(mut self, width: u16, height: u16) -> Self {
113 self.canvas_size = CanvasPoint(width, height);
114 self
115 }
116
117 /// Set the center of the graph. Returns an updated [`GraphOptions`] for chaining.
118 pub fn center(mut self, x: f64, y: f64) -> Self {
119 self.center = GraphPoint(x, y);
120 self
121 }
122
123 /// Set the scale of the graph. Returns an updated [`GraphOptions`] for chaining.
124 pub fn scale(mut self, x: f64, y: f64) -> Self {
125 self.scale = GraphPoint(x, y);
126 self
127 }
128
129 /// Set whether to scale the x- and y-axes together. Returns an updated [`GraphOptions`] for
130 /// chaining.
131 pub fn square_scale(mut self, square_scale: bool) -> Self {
132 self.square_scale = square_scale;
133 self
134 }
135
136 /// Set whether to label the canvas boundaries with their corresponding graph values. Returns
137 /// an updated [`GraphOptions`] for chaining.
138 pub fn label_canvas_boundaries(mut self, label_canvas_boundaries: bool) -> Self {
139 self.label_canvas_boundaries = label_canvas_boundaries;
140 self
141 }
142
143 /// Set the number of graph units between each major grid line. Returns an updated
144 /// [`GraphOptions`] for chaining.
145 pub fn major_grid_spacing(mut self, x: f64, y: f64) -> Self {
146 self.major_grid_spacing = GraphPoint(x, y);
147 self
148 }
149
150 /// Set the number of spaces to divide each major grid line into. Returns an updated
151 /// [`GraphOptions`] for chaining.
152 pub fn major_grid_divisions(mut self, x: u8, y: u8) -> Self {
153 self.major_grid_divisions = (x, y);
154 self
155 }
156
157 /// Set the opacity of the major grid lines. Returns an updated [`GraphOptions`] for chaining.
158 pub fn major_grid_opacity(mut self, major_grid_opacity: f64) -> Self {
159 self.major_grid_opacity = major_grid_opacity;
160 self
161 }
162
163 /// Set the opacity of the minor grid lines. Returns an updated [`GraphOptions`] for chaining.
164 pub fn minor_grid_opacity(mut self, minor_grid_opacity: f64) -> Self {
165 self.minor_grid_opacity = minor_grid_opacity;
166 self
167 }
168}
169
170impl GraphOptions {
171 /// Converts an x-value in **graph** space to an x-value in **canvas** space.
172 pub(crate) fn x_to_canvas(&self, x: f64) -> f64 {
173 let graph_space_range = self.scale.0 * 2.0;
174
175 // normalize x-value to [0.0, 1.0], where 0.0 indicates left-edge of visible graph, 1.0
176 // indicates right-edge of visible graph
177 let normalized = (x - self.center.0) / graph_space_range + 0.5;
178
179 // convert normalized x-value to canvas space
180 normalized * self.canvas_size.0 as f64
181 }
182
183 /// Converts a y-value in **graph** space to a y-value in **canvas** space.
184 pub(crate) fn y_to_canvas(&self, y: f64) -> f64 {
185 let graph_space_range = self.scale.1 * 2.0;
186
187 // normalize y-value to [0.0, 1.0], then flip the normalized value so, 0.0 is top, 1.0 is bottom
188 // this is because the y-axis is flipped in graph space
189 let normalized = 0.5 - (y - self.center.1) / graph_space_range;
190
191 // convert normalized y-value to canvas space
192 normalized * self.canvas_size.1 as f64
193 }
194
195 /// Converts a point in **graph** space to **canvas** space.
196 pub fn to_canvas(&self, point: GraphPoint<f64>) -> CanvasPoint<f64> {
197 CanvasPoint(
198 self.x_to_canvas(point.0),
199 self.y_to_canvas(point.1),
200 )
201 }
202
203 /// Converts an x-value in **canvas** space to an x-value in **graph** space.
204 pub(crate) fn x_to_graph(&self, x: f64) -> f64 {
205 // normalize x-value to [0.0, 1.0], where 0.0 indicates left-edge of canvas, 1.0 indicates
206 // right-edge of canvas (x should always be positive)
207 let normalized = x / self.canvas_size.0 as f64;
208
209 // convert normalized x-value to graph space
210 let graph_space_range = self.scale.0 * 2.0;
211 let left_edge_graph_space = self.center.0 - self.scale.0;
212
213 normalized * graph_space_range + left_edge_graph_space
214 }
215
216 /// Converts a y-value in **canvas** space to a y-value in **graph** space.
217 pub(crate) fn y_to_graph(&self, y: f64) -> f64 {
218 // normalize y-value to [0.0, 1.0], then flip the normalized value so, 0.0 is bottom, 1.0 is top
219 // this is because the y-axis is flipped in canvas space
220 let normalized = 1.0 - y / self.canvas_size.1 as f64;
221
222 // convert normalized y-value to graph space
223 let graph_space_range = self.scale.1 * 2.0;
224 let bottom_edge_graph_space = self.center.1 - self.scale.1;
225
226 normalized * graph_space_range + bottom_edge_graph_space
227 }
228
229 /// Converts a point in **canvas** space to **graph** space.
230 pub fn to_graph(&self, point: CanvasPoint<f64>) -> GraphPoint<f64> {
231 GraphPoint(
232 self.x_to_graph(point.0),
233 self.y_to_graph(point.1),
234 )
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use crate::graph::round_to;
241 use super::*;
242
243 /// Test the conversion functions from canvas to graph space.
244 #[test]
245 fn canvas_to_graph() {
246 let options = GraphOptions {
247 canvas_size: CanvasPoint(465, 917),
248 center: GraphPoint(-3.0, 2.41),
249 scale: GraphPoint(3.59, 5.69),
250 ..Default::default()
251 };
252
253 assert_eq!(
254 options.x_to_graph(0.0),
255 options.center.0 - options.scale.0,
256 );
257 assert_eq!(
258 options.x_to_graph(options.canvas_size.0 as f64),
259 options.center.0 + options.scale.0,
260 );
261 assert_eq!(
262 options.y_to_graph(0.0),
263 options.center.1 + options.scale.1,
264 );
265 assert_eq!(
266 options.y_to_graph(options.canvas_size.1 as f64),
267 options.center.1 - options.scale.1,
268 );
269 }
270
271 /// Test the conversion functions from graph to canvas space.
272 #[test]
273 fn graph_to_canvas() {
274 let options = GraphOptions {
275 canvas_size: CanvasPoint(465, 917),
276 center: GraphPoint(-3.0, 2.41),
277 scale: GraphPoint(3.59, 5.69),
278 ..Default::default()
279 };
280
281 assert_eq!(
282 options.x_to_canvas(options.center.0 - options.scale.0),
283 0.0,
284 );
285 assert_eq!(
286 options.x_to_canvas(options.center.0 + options.scale.0),
287 options.canvas_size.0 as f64,
288 );
289 // precision is wonky with this one
290 assert_eq!(
291 round_to(options.y_to_canvas(options.center.1 + options.scale.1), 1e-6),
292 0.0,
293 );
294 assert_eq!(
295 options.y_to_canvas(options.center.1 - options.scale.1),
296 options.canvas_size.1 as f64,
297 );
298 }
299}