1use crate::coord::Coord;
2use crate::render::backend::{DrawBackend, LineStyle, 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 let top = scale.axis_position_opposite();
22 let edge_y = if top {
23 plot_area.y
24 } else {
25 plot_area.y + plot_area.height
26 };
27 let dir = if top { -1.0 } else { 1.0 };
28
29 if axis_line.visible {
31 backend.draw_line(
32 &[
33 (plot_area.x, edge_y),
34 (plot_area.x + plot_area.width, edge_y),
35 ],
36 &LineStyle {
37 color: axis_line.color,
38 width: axis_line.width,
39 alpha: 1.0,
40 linetype: axis_line.linetype,
41 },
42 )?;
43 }
44
45 let dodge = theme.axis_text_x_dodge.max(1);
47 for (i, (pos, label)) in breaks.iter().enumerate() {
48 let (px, _py) = coord.transform((*pos, 0.0), plot_area);
49
50 if axis_ticks.visible {
51 backend.draw_line(
52 &[(px, edge_y), (px, edge_y + dir * tick_len)],
53 &LineStyle {
54 color: axis_ticks.color,
55 width: axis_ticks.width,
56 alpha: 1.0,
57 linetype: axis_ticks.linetype,
58 },
59 )?;
60 }
61
62 if theme.axis_text_x.visible {
63 let family = if theme.axis_text_x.family.is_empty() {
64 None
65 } else {
66 Some(theme.axis_text_x.family.clone())
67 };
68 let anchor = if theme.axis_text_x.angle.abs() > 10.0 {
70 TextAnchor::End
71 } else {
72 TextAnchor::Middle
73 };
74 let row_offset = (i % dodge) as f64 * (theme.axis_text_x.size + 2.0);
76 backend.draw_text(
77 label,
78 (
79 px,
80 edge_y
81 + dir
82 * (tick_len
83 + theme.legend_spacing / 2.0
84 + theme.axis_text_x.size / 2.0
85 + row_offset),
86 ),
87 &TextStyle {
88 color: theme.axis_text_x.color,
89 size: theme.axis_text_x.size,
90 anchor,
91 angle: theme.axis_text_x.angle,
92 family,
93 face: theme.axis_text_x.face,
94 },
95 )?;
96 }
97 }
98
99 if theme.axis_minor_ticks && axis_ticks.visible {
101 for pos in minor_breaks(&breaks) {
102 let (px, _) = coord.transform((pos, 0.0), plot_area);
103 backend.draw_line(
104 &[(px, edge_y), (px, edge_y + dir * tick_len * 0.5)],
105 &LineStyle {
106 color: axis_ticks.color,
107 width: axis_ticks.width,
108 alpha: 1.0,
109 linetype: axis_ticks.linetype,
110 },
111 )?;
112 }
113 }
114
115 let title = scale.name();
117 if !title.is_empty() && theme.axis_title_x.visible {
118 let hjust = theme.axis_title_x.hjust.clamp(0.0, 1.0);
119 let center_x = plot_area.x + hjust * plot_area.width;
120 let anchor = if hjust <= 0.02 {
121 TextAnchor::Start
122 } else if hjust >= 0.98 {
123 TextAnchor::End
124 } else {
125 TextAnchor::Middle
126 };
127 let title_y = edge_y
128 + dir * (tick_len + theme.axis_text_x.size + 8.0 + theme.axis_title_x.size / 2.0);
129 let family = if theme.axis_title_x.family.is_empty() {
130 None
131 } else {
132 Some(theme.axis_title_x.family.clone())
133 };
134 backend.draw_text(
135 title,
136 (center_x, title_y),
137 &TextStyle {
138 color: theme.axis_title_x.color,
139 size: theme.axis_title_x.size,
140 anchor,
141 angle: 0.0,
142 family,
143 face: theme.axis_title_x.face,
144 },
145 )?;
146 }
147
148 Ok(())
149}
150
151pub fn draw_y_axis(
153 scale: &dyn Scale,
154 coord: &dyn Coord,
155 theme: &Theme,
156 plot_area: &Rect,
157 backend: &mut dyn DrawBackend,
158) -> Result<(), RenderError> {
159 let breaks = scale.breaks();
160 let tick_len = theme.axis_ticks_length;
161 let axis_line = theme.get_axis_line_y();
162 let axis_ticks = theme.get_axis_ticks_y();
163
164 if axis_line.visible {
166 let top = (plot_area.x, plot_area.y);
167 let bottom = (plot_area.x, plot_area.y + plot_area.height);
168 backend.draw_line(
169 &[top, bottom],
170 &LineStyle {
171 color: axis_line.color,
172 width: axis_line.width,
173 alpha: 1.0,
174 linetype: axis_line.linetype,
175 },
176 )?;
177 }
178
179 for (pos, label) in &breaks {
181 let (_, py) = coord.transform((0.0, *pos), plot_area);
182
183 if axis_ticks.visible {
184 backend.draw_line(
185 &[(plot_area.x - tick_len, py), (plot_area.x, py)],
186 &LineStyle {
187 color: axis_ticks.color,
188 width: axis_ticks.width,
189 alpha: 1.0,
190 linetype: axis_ticks.linetype,
191 },
192 )?;
193 }
194
195 if theme.axis_text_y.visible {
196 let family = if theme.axis_text_y.family.is_empty() {
197 None
198 } else {
199 Some(theme.axis_text_y.family.clone())
200 };
201 backend.draw_text(
202 label,
203 (plot_area.x - tick_len - theme.legend_spacing, py),
204 &TextStyle {
205 color: theme.axis_text_y.color,
206 size: theme.axis_text_y.size,
207 anchor: TextAnchor::End,
208 angle: theme.axis_text_y.angle,
209 family,
210 face: theme.axis_text_y.face,
211 },
212 )?;
213 }
214 }
215
216 if theme.axis_minor_ticks && axis_ticks.visible {
218 for pos in minor_breaks(&breaks) {
219 let (_, py) = coord.transform((0.0, pos), plot_area);
220 backend.draw_line(
221 &[(plot_area.x - tick_len * 0.5, py), (plot_area.x, py)],
222 &LineStyle {
223 color: axis_ticks.color,
224 width: axis_ticks.width,
225 alpha: 1.0,
226 linetype: axis_ticks.linetype,
227 },
228 )?;
229 }
230 }
231
232 let title = scale.name();
234 if !title.is_empty() && theme.axis_title_y.visible {
235 let title_x = plot_area.x - tick_len - theme.axis_text_y.size * 3.5 - theme.legend_spacing;
236 let center_y = plot_area.y + plot_area.height / 2.0;
237 let family = if theme.axis_title_y.family.is_empty() {
238 None
239 } else {
240 Some(theme.axis_title_y.family.clone())
241 };
242 backend.draw_text(
243 title,
244 (title_x, center_y),
245 &TextStyle {
246 color: theme.axis_title_y.color,
247 size: theme.axis_title_y.size,
248 anchor: TextAnchor::Middle,
249 angle: 270.0,
250 family,
251 face: theme.axis_title_y.face,
252 },
253 )?;
254 }
255
256 Ok(())
257}
258
259pub fn draw_sec_y_axis(
261 primary_scale: &dyn Scale,
262 sec_axis: &crate::scale::sec_axis::SecAxis,
263 coord: &dyn Coord,
264 theme: &Theme,
265 plot_area: &Rect,
266 backend: &mut dyn DrawBackend,
267) -> Result<(), RenderError> {
268 let breaks = primary_scale.breaks();
269 let tick_len = theme.axis_ticks_length;
270 let axis_line = theme.get_axis_line_y();
271 let axis_ticks = theme.get_axis_ticks_y();
272
273 let right_x = plot_area.x + plot_area.width;
274
275 if axis_line.visible {
277 backend.draw_line(
278 &[
279 (right_x, plot_area.y),
280 (right_x, plot_area.y + plot_area.height),
281 ],
282 &LineStyle {
283 color: axis_line.color,
284 width: axis_line.width,
285 alpha: 1.0,
286 linetype: axis_line.linetype,
287 },
288 )?;
289 }
290
291 for (pos, label) in &breaks {
293 let (_, py) = coord.transform((0.0, *pos), plot_area);
294
295 if axis_ticks.visible {
296 backend.draw_line(
297 &[(right_x, py), (right_x + tick_len, py)],
298 &LineStyle {
299 color: axis_ticks.color,
300 width: axis_ticks.width,
301 alpha: 1.0,
302 linetype: axis_ticks.linetype,
303 },
304 )?;
305 }
306
307 if theme.axis_text_y.visible {
308 let sec_label = if let Ok(v) = label.parse::<f64>() {
310 let transformed = sec_axis.transform_value(v);
311 crate::scale::util::format_number(transformed)
312 } else {
313 label.clone()
314 };
315
316 let family = if theme.axis_text_y.family.is_empty() {
317 None
318 } else {
319 Some(theme.axis_text_y.family.clone())
320 };
321 backend.draw_text(
322 &sec_label,
323 (right_x + tick_len + theme.legend_spacing, py),
324 &TextStyle {
325 color: theme.axis_text_y.color,
326 size: theme.axis_text_y.size,
327 anchor: TextAnchor::Start,
328 angle: theme.axis_text_y.angle,
329 family,
330 face: theme.axis_text_y.face,
331 },
332 )?;
333 }
334 }
335
336 if !sec_axis.name.is_empty() && theme.axis_title_y.visible {
338 let title_x = right_x + tick_len + theme.axis_text_y.size * 3.5 + theme.legend_spacing;
339 let center_y = plot_area.y + plot_area.height / 2.0;
340 let family = if theme.axis_title_y.family.is_empty() {
341 None
342 } else {
343 Some(theme.axis_title_y.family.clone())
344 };
345 backend.draw_text(
346 &sec_axis.name,
347 (title_x, center_y),
348 &TextStyle {
349 color: theme.axis_title_y.color,
350 size: theme.axis_title_y.size,
351 anchor: TextAnchor::Middle,
352 angle: 90.0,
353 family,
354 face: theme.axis_title_y.face,
355 },
356 )?;
357 }
358
359 Ok(())
360}
361
362fn minor_breaks(major: &[(f64, String)]) -> Vec<f64> {
364 if major.len() < 2 {
365 return vec![];
366 }
367 let mut minors = Vec::with_capacity(major.len() - 1);
368 for pair in major.windows(2) {
369 minors.push((pair[0].0 + pair[1].0) / 2.0);
370 }
371 minors
372}
373
374pub fn draw_gridlines(
376 x_scale: &dyn Scale,
377 y_scale: &dyn Scale,
378 coord: &dyn Coord,
379 theme: &Theme,
380 plot_area: &Rect,
381 backend: &mut dyn DrawBackend,
382) -> Result<(), RenderError> {
383 let major_x = theme.get_panel_grid_major_x();
384 let major_y = theme.get_panel_grid_major_y();
385 let minor_x = theme.get_panel_grid_minor_x();
386 let minor_y = theme.get_panel_grid_minor_y();
387
388 let x_breaks = x_scale.breaks();
389 let y_breaks = y_scale.breaks();
390
391 if minor_x.visible {
393 for pos in minor_breaks(&x_breaks) {
394 let (px, _) = coord.transform((pos, 0.0), plot_area);
395 backend.draw_line(
396 &[(px, plot_area.y), (px, plot_area.y + plot_area.height)],
397 &LineStyle {
398 color: minor_x.color,
399 width: minor_x.width,
400 alpha: 1.0,
401 linetype: minor_x.linetype,
402 },
403 )?;
404 }
405 }
406
407 if minor_y.visible {
409 for pos in minor_breaks(&y_breaks) {
410 let (_, py) = coord.transform((0.0, pos), plot_area);
411 backend.draw_line(
412 &[(plot_area.x, py), (plot_area.x + plot_area.width, py)],
413 &LineStyle {
414 color: minor_y.color,
415 width: minor_y.width,
416 alpha: 1.0,
417 linetype: minor_y.linetype,
418 },
419 )?;
420 }
421 }
422
423 if major_x.visible {
425 for (pos, _) in &x_breaks {
426 let (px, _) = coord.transform((*pos, 0.0), plot_area);
427 backend.draw_line(
428 &[(px, plot_area.y), (px, plot_area.y + plot_area.height)],
429 &LineStyle {
430 color: major_x.color,
431 width: major_x.width,
432 alpha: 1.0,
433 linetype: major_x.linetype,
434 },
435 )?;
436 }
437 }
438
439 if major_y.visible {
441 for (pos, _) in &y_breaks {
442 let (_, py) = coord.transform((0.0, *pos), plot_area);
443 backend.draw_line(
444 &[(plot_area.x, py), (plot_area.x + plot_area.width, py)],
445 &LineStyle {
446 color: major_y.color,
447 width: major_y.width,
448 alpha: 1.0,
449 linetype: major_y.linetype,
450 },
451 )?;
452 }
453 }
454
455 Ok(())
456}