1use crate::coord::Coord;
2use crate::render::backend::{DrawBackend, LineStyle, Linetype, TextAnchor, TextStyle};
3use crate::render::{Rect, RenderError};
4use crate::scale::Scale;
5use crate::theme::Theme;
6
7pub fn draw_x_axis(
9 scale: &dyn Scale,
10 coord: &dyn Coord,
11 theme: &Theme,
12 plot_area: &Rect,
13 backend: &mut dyn DrawBackend,
14) -> Result<(), RenderError> {
15 let breaks = scale.breaks();
16 let tick_len = theme.axis_ticks_length;
17 let axis_line = theme.get_axis_line_x();
18 let axis_ticks = theme.get_axis_ticks_x();
19
20 if axis_line.visible {
22 let left = (plot_area.x, plot_area.y + plot_area.height);
23 let right = (
24 plot_area.x + plot_area.width,
25 plot_area.y + plot_area.height,
26 );
27 backend.draw_line(
28 &[left, right],
29 &LineStyle {
30 color: axis_line.color,
31 width: axis_line.width,
32 alpha: 1.0,
33 linetype: Linetype::Solid,
34 },
35 )?;
36 }
37
38 for (pos, label) in &breaks {
40 let (px, _py) = coord.transform((*pos, 0.0), plot_area);
41 let tick_y = plot_area.y + plot_area.height;
42
43 if axis_ticks.visible {
44 backend.draw_line(
45 &[(px, tick_y), (px, tick_y + tick_len)],
46 &LineStyle {
47 color: axis_ticks.color,
48 width: axis_ticks.width,
49 alpha: 1.0,
50 linetype: Linetype::Solid,
51 },
52 )?;
53 }
54
55 if theme.axis_text_x.visible {
56 let family = if theme.axis_text_x.family.is_empty() {
57 None
58 } else {
59 Some(theme.axis_text_x.family.clone())
60 };
61 let anchor = if theme.axis_text_x.angle.abs() > 10.0 {
63 TextAnchor::End
64 } else {
65 TextAnchor::Middle
66 };
67 backend.draw_text(
68 label,
69 (
70 px,
71 tick_y + tick_len + theme.legend_spacing / 2.0 + theme.axis_text_x.size / 2.0,
72 ),
73 &TextStyle {
74 color: theme.axis_text_x.color,
75 size: theme.axis_text_x.size,
76 anchor,
77 angle: theme.axis_text_x.angle,
78 family,
79 },
80 )?;
81 }
82 }
83
84 let title = scale.name();
86 if !title.is_empty() && theme.axis_title_x.visible {
87 let center_x = plot_area.x + plot_area.width / 2.0;
88 let title_y = plot_area.y
89 + plot_area.height
90 + tick_len
91 + theme.axis_text_x.size
92 + 8.0
93 + theme.axis_title_x.size / 2.0;
94 let family = if theme.axis_title_x.family.is_empty() {
95 None
96 } else {
97 Some(theme.axis_title_x.family.clone())
98 };
99 backend.draw_text(
100 title,
101 (center_x, title_y),
102 &TextStyle {
103 color: theme.axis_title_x.color,
104 size: theme.axis_title_x.size,
105 anchor: TextAnchor::Middle,
106 angle: 0.0,
107 family,
108 },
109 )?;
110 }
111
112 Ok(())
113}
114
115pub fn draw_y_axis(
117 scale: &dyn Scale,
118 coord: &dyn Coord,
119 theme: &Theme,
120 plot_area: &Rect,
121 backend: &mut dyn DrawBackend,
122) -> Result<(), RenderError> {
123 let breaks = scale.breaks();
124 let tick_len = theme.axis_ticks_length;
125 let axis_line = theme.get_axis_line_y();
126 let axis_ticks = theme.get_axis_ticks_y();
127
128 if axis_line.visible {
130 let top = (plot_area.x, plot_area.y);
131 let bottom = (plot_area.x, plot_area.y + plot_area.height);
132 backend.draw_line(
133 &[top, bottom],
134 &LineStyle {
135 color: axis_line.color,
136 width: axis_line.width,
137 alpha: 1.0,
138 linetype: Linetype::Solid,
139 },
140 )?;
141 }
142
143 for (pos, label) in &breaks {
145 let (_, py) = coord.transform((0.0, *pos), plot_area);
146
147 if axis_ticks.visible {
148 backend.draw_line(
149 &[(plot_area.x - tick_len, py), (plot_area.x, py)],
150 &LineStyle {
151 color: axis_ticks.color,
152 width: axis_ticks.width,
153 alpha: 1.0,
154 linetype: Linetype::Solid,
155 },
156 )?;
157 }
158
159 if theme.axis_text_y.visible {
160 let family = if theme.axis_text_y.family.is_empty() {
161 None
162 } else {
163 Some(theme.axis_text_y.family.clone())
164 };
165 backend.draw_text(
166 label,
167 (plot_area.x - tick_len - theme.legend_spacing, py),
168 &TextStyle {
169 color: theme.axis_text_y.color,
170 size: theme.axis_text_y.size,
171 anchor: TextAnchor::End,
172 angle: theme.axis_text_y.angle,
173 family,
174 },
175 )?;
176 }
177 }
178
179 let title = scale.name();
181 if !title.is_empty() && theme.axis_title_y.visible {
182 let title_x = plot_area.x - tick_len - theme.axis_text_y.size * 3.5 - theme.legend_spacing;
183 let center_y = plot_area.y + plot_area.height / 2.0;
184 let family = if theme.axis_title_y.family.is_empty() {
185 None
186 } else {
187 Some(theme.axis_title_y.family.clone())
188 };
189 backend.draw_text(
190 title,
191 (title_x, center_y),
192 &TextStyle {
193 color: theme.axis_title_y.color,
194 size: theme.axis_title_y.size,
195 anchor: TextAnchor::Middle,
196 angle: 270.0,
197 family,
198 },
199 )?;
200 }
201
202 Ok(())
203}
204
205pub fn draw_sec_y_axis(
207 primary_scale: &dyn Scale,
208 sec_axis: &crate::scale::sec_axis::SecAxis,
209 coord: &dyn Coord,
210 theme: &Theme,
211 plot_area: &Rect,
212 backend: &mut dyn DrawBackend,
213) -> Result<(), RenderError> {
214 let breaks = primary_scale.breaks();
215 let tick_len = theme.axis_ticks_length;
216 let axis_line = theme.get_axis_line_y();
217 let axis_ticks = theme.get_axis_ticks_y();
218
219 let right_x = plot_area.x + plot_area.width;
220
221 if axis_line.visible {
223 backend.draw_line(
224 &[
225 (right_x, plot_area.y),
226 (right_x, plot_area.y + plot_area.height),
227 ],
228 &LineStyle {
229 color: axis_line.color,
230 width: axis_line.width,
231 alpha: 1.0,
232 linetype: Linetype::Solid,
233 },
234 )?;
235 }
236
237 for (pos, label) in &breaks {
239 let (_, py) = coord.transform((0.0, *pos), plot_area);
240
241 if axis_ticks.visible {
242 backend.draw_line(
243 &[(right_x, py), (right_x + tick_len, py)],
244 &LineStyle {
245 color: axis_ticks.color,
246 width: axis_ticks.width,
247 alpha: 1.0,
248 linetype: Linetype::Solid,
249 },
250 )?;
251 }
252
253 if theme.axis_text_y.visible {
254 let sec_label = if let Ok(v) = label.parse::<f64>() {
256 let transformed = sec_axis.transform_value(v);
257 crate::scale::util::format_number(transformed)
258 } else {
259 label.clone()
260 };
261
262 let family = if theme.axis_text_y.family.is_empty() {
263 None
264 } else {
265 Some(theme.axis_text_y.family.clone())
266 };
267 backend.draw_text(
268 &sec_label,
269 (right_x + tick_len + theme.legend_spacing, py),
270 &TextStyle {
271 color: theme.axis_text_y.color,
272 size: theme.axis_text_y.size,
273 anchor: TextAnchor::Start,
274 angle: theme.axis_text_y.angle,
275 family,
276 },
277 )?;
278 }
279 }
280
281 if !sec_axis.name.is_empty() && theme.axis_title_y.visible {
283 let title_x = right_x + tick_len + theme.axis_text_y.size * 3.5 + theme.legend_spacing;
284 let center_y = plot_area.y + plot_area.height / 2.0;
285 let family = if theme.axis_title_y.family.is_empty() {
286 None
287 } else {
288 Some(theme.axis_title_y.family.clone())
289 };
290 backend.draw_text(
291 &sec_axis.name,
292 (title_x, center_y),
293 &TextStyle {
294 color: theme.axis_title_y.color,
295 size: theme.axis_title_y.size,
296 anchor: TextAnchor::Middle,
297 angle: 90.0,
298 family,
299 },
300 )?;
301 }
302
303 Ok(())
304}
305
306fn minor_breaks(major: &[(f64, String)]) -> Vec<f64> {
308 if major.len() < 2 {
309 return vec![];
310 }
311 let mut minors = Vec::with_capacity(major.len() - 1);
312 for pair in major.windows(2) {
313 minors.push((pair[0].0 + pair[1].0) / 2.0);
314 }
315 minors
316}
317
318pub fn draw_gridlines(
320 x_scale: &dyn Scale,
321 y_scale: &dyn Scale,
322 coord: &dyn Coord,
323 theme: &Theme,
324 plot_area: &Rect,
325 backend: &mut dyn DrawBackend,
326) -> Result<(), RenderError> {
327 let major_x = theme.get_panel_grid_major_x();
328 let major_y = theme.get_panel_grid_major_y();
329 let minor_x = theme.get_panel_grid_minor_x();
330 let minor_y = theme.get_panel_grid_minor_y();
331
332 let x_breaks = x_scale.breaks();
333 let y_breaks = y_scale.breaks();
334
335 if minor_x.visible {
337 for pos in minor_breaks(&x_breaks) {
338 let (px, _) = coord.transform((pos, 0.0), plot_area);
339 backend.draw_line(
340 &[(px, plot_area.y), (px, plot_area.y + plot_area.height)],
341 &LineStyle {
342 color: minor_x.color,
343 width: minor_x.width,
344 alpha: 1.0,
345 linetype: Linetype::Solid,
346 },
347 )?;
348 }
349 }
350
351 if minor_y.visible {
353 for pos in minor_breaks(&y_breaks) {
354 let (_, py) = coord.transform((0.0, pos), plot_area);
355 backend.draw_line(
356 &[(plot_area.x, py), (plot_area.x + plot_area.width, py)],
357 &LineStyle {
358 color: minor_y.color,
359 width: minor_y.width,
360 alpha: 1.0,
361 linetype: Linetype::Solid,
362 },
363 )?;
364 }
365 }
366
367 if major_x.visible {
369 for (pos, _) in &x_breaks {
370 let (px, _) = coord.transform((*pos, 0.0), plot_area);
371 backend.draw_line(
372 &[(px, plot_area.y), (px, plot_area.y + plot_area.height)],
373 &LineStyle {
374 color: major_x.color,
375 width: major_x.width,
376 alpha: 1.0,
377 linetype: Linetype::Solid,
378 },
379 )?;
380 }
381 }
382
383 if major_y.visible {
385 for (pos, _) in &y_breaks {
386 let (_, py) = coord.transform((0.0, *pos), plot_area);
387 backend.draw_line(
388 &[(plot_area.x, py), (plot_area.x + plot_area.width, py)],
389 &LineStyle {
390 color: major_y.color,
391 width: major_y.width,
392 alpha: 1.0,
393 linetype: Linetype::Solid,
394 },
395 )?;
396 }
397 }
398
399 Ok(())
400}