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 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: Linetype::Solid,
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: Linetype::Solid,
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 },
94 )?;
95 }
96 }
97
98 let title = scale.name();
100 if !title.is_empty() && theme.axis_title_x.visible {
101 let center_x = plot_area.x + plot_area.width / 2.0;
102 let title_y = edge_y
103 + dir * (tick_len + theme.axis_text_x.size + 8.0 + theme.axis_title_x.size / 2.0);
104 let family = if theme.axis_title_x.family.is_empty() {
105 None
106 } else {
107 Some(theme.axis_title_x.family.clone())
108 };
109 backend.draw_text(
110 title,
111 (center_x, title_y),
112 &TextStyle {
113 color: theme.axis_title_x.color,
114 size: theme.axis_title_x.size,
115 anchor: TextAnchor::Middle,
116 angle: 0.0,
117 family,
118 },
119 )?;
120 }
121
122 Ok(())
123}
124
125pub fn draw_y_axis(
127 scale: &dyn Scale,
128 coord: &dyn Coord,
129 theme: &Theme,
130 plot_area: &Rect,
131 backend: &mut dyn DrawBackend,
132) -> Result<(), RenderError> {
133 let breaks = scale.breaks();
134 let tick_len = theme.axis_ticks_length;
135 let axis_line = theme.get_axis_line_y();
136 let axis_ticks = theme.get_axis_ticks_y();
137
138 if axis_line.visible {
140 let top = (plot_area.x, plot_area.y);
141 let bottom = (plot_area.x, plot_area.y + plot_area.height);
142 backend.draw_line(
143 &[top, bottom],
144 &LineStyle {
145 color: axis_line.color,
146 width: axis_line.width,
147 alpha: 1.0,
148 linetype: Linetype::Solid,
149 },
150 )?;
151 }
152
153 for (pos, label) in &breaks {
155 let (_, py) = coord.transform((0.0, *pos), plot_area);
156
157 if axis_ticks.visible {
158 backend.draw_line(
159 &[(plot_area.x - tick_len, py), (plot_area.x, py)],
160 &LineStyle {
161 color: axis_ticks.color,
162 width: axis_ticks.width,
163 alpha: 1.0,
164 linetype: Linetype::Solid,
165 },
166 )?;
167 }
168
169 if theme.axis_text_y.visible {
170 let family = if theme.axis_text_y.family.is_empty() {
171 None
172 } else {
173 Some(theme.axis_text_y.family.clone())
174 };
175 backend.draw_text(
176 label,
177 (plot_area.x - tick_len - theme.legend_spacing, py),
178 &TextStyle {
179 color: theme.axis_text_y.color,
180 size: theme.axis_text_y.size,
181 anchor: TextAnchor::End,
182 angle: theme.axis_text_y.angle,
183 family,
184 },
185 )?;
186 }
187 }
188
189 let title = scale.name();
191 if !title.is_empty() && theme.axis_title_y.visible {
192 let title_x = plot_area.x - tick_len - theme.axis_text_y.size * 3.5 - theme.legend_spacing;
193 let center_y = plot_area.y + plot_area.height / 2.0;
194 let family = if theme.axis_title_y.family.is_empty() {
195 None
196 } else {
197 Some(theme.axis_title_y.family.clone())
198 };
199 backend.draw_text(
200 title,
201 (title_x, center_y),
202 &TextStyle {
203 color: theme.axis_title_y.color,
204 size: theme.axis_title_y.size,
205 anchor: TextAnchor::Middle,
206 angle: 270.0,
207 family,
208 },
209 )?;
210 }
211
212 Ok(())
213}
214
215pub fn draw_sec_y_axis(
217 primary_scale: &dyn Scale,
218 sec_axis: &crate::scale::sec_axis::SecAxis,
219 coord: &dyn Coord,
220 theme: &Theme,
221 plot_area: &Rect,
222 backend: &mut dyn DrawBackend,
223) -> Result<(), RenderError> {
224 let breaks = primary_scale.breaks();
225 let tick_len = theme.axis_ticks_length;
226 let axis_line = theme.get_axis_line_y();
227 let axis_ticks = theme.get_axis_ticks_y();
228
229 let right_x = plot_area.x + plot_area.width;
230
231 if axis_line.visible {
233 backend.draw_line(
234 &[
235 (right_x, plot_area.y),
236 (right_x, plot_area.y + plot_area.height),
237 ],
238 &LineStyle {
239 color: axis_line.color,
240 width: axis_line.width,
241 alpha: 1.0,
242 linetype: Linetype::Solid,
243 },
244 )?;
245 }
246
247 for (pos, label) in &breaks {
249 let (_, py) = coord.transform((0.0, *pos), plot_area);
250
251 if axis_ticks.visible {
252 backend.draw_line(
253 &[(right_x, py), (right_x + tick_len, py)],
254 &LineStyle {
255 color: axis_ticks.color,
256 width: axis_ticks.width,
257 alpha: 1.0,
258 linetype: Linetype::Solid,
259 },
260 )?;
261 }
262
263 if theme.axis_text_y.visible {
264 let sec_label = if let Ok(v) = label.parse::<f64>() {
266 let transformed = sec_axis.transform_value(v);
267 crate::scale::util::format_number(transformed)
268 } else {
269 label.clone()
270 };
271
272 let family = if theme.axis_text_y.family.is_empty() {
273 None
274 } else {
275 Some(theme.axis_text_y.family.clone())
276 };
277 backend.draw_text(
278 &sec_label,
279 (right_x + tick_len + theme.legend_spacing, py),
280 &TextStyle {
281 color: theme.axis_text_y.color,
282 size: theme.axis_text_y.size,
283 anchor: TextAnchor::Start,
284 angle: theme.axis_text_y.angle,
285 family,
286 },
287 )?;
288 }
289 }
290
291 if !sec_axis.name.is_empty() && theme.axis_title_y.visible {
293 let title_x = right_x + tick_len + theme.axis_text_y.size * 3.5 + theme.legend_spacing;
294 let center_y = plot_area.y + plot_area.height / 2.0;
295 let family = if theme.axis_title_y.family.is_empty() {
296 None
297 } else {
298 Some(theme.axis_title_y.family.clone())
299 };
300 backend.draw_text(
301 &sec_axis.name,
302 (title_x, center_y),
303 &TextStyle {
304 color: theme.axis_title_y.color,
305 size: theme.axis_title_y.size,
306 anchor: TextAnchor::Middle,
307 angle: 90.0,
308 family,
309 },
310 )?;
311 }
312
313 Ok(())
314}
315
316fn minor_breaks(major: &[(f64, String)]) -> Vec<f64> {
318 if major.len() < 2 {
319 return vec![];
320 }
321 let mut minors = Vec::with_capacity(major.len() - 1);
322 for pair in major.windows(2) {
323 minors.push((pair[0].0 + pair[1].0) / 2.0);
324 }
325 minors
326}
327
328pub fn draw_gridlines(
330 x_scale: &dyn Scale,
331 y_scale: &dyn Scale,
332 coord: &dyn Coord,
333 theme: &Theme,
334 plot_area: &Rect,
335 backend: &mut dyn DrawBackend,
336) -> Result<(), RenderError> {
337 let major_x = theme.get_panel_grid_major_x();
338 let major_y = theme.get_panel_grid_major_y();
339 let minor_x = theme.get_panel_grid_minor_x();
340 let minor_y = theme.get_panel_grid_minor_y();
341
342 let x_breaks = x_scale.breaks();
343 let y_breaks = y_scale.breaks();
344
345 if minor_x.visible {
347 for pos in minor_breaks(&x_breaks) {
348 let (px, _) = coord.transform((pos, 0.0), plot_area);
349 backend.draw_line(
350 &[(px, plot_area.y), (px, plot_area.y + plot_area.height)],
351 &LineStyle {
352 color: minor_x.color,
353 width: minor_x.width,
354 alpha: 1.0,
355 linetype: Linetype::Solid,
356 },
357 )?;
358 }
359 }
360
361 if minor_y.visible {
363 for pos in minor_breaks(&y_breaks) {
364 let (_, py) = coord.transform((0.0, pos), plot_area);
365 backend.draw_line(
366 &[(plot_area.x, py), (plot_area.x + plot_area.width, py)],
367 &LineStyle {
368 color: minor_y.color,
369 width: minor_y.width,
370 alpha: 1.0,
371 linetype: Linetype::Solid,
372 },
373 )?;
374 }
375 }
376
377 if major_x.visible {
379 for (pos, _) in &x_breaks {
380 let (px, _) = coord.transform((*pos, 0.0), plot_area);
381 backend.draw_line(
382 &[(px, plot_area.y), (px, plot_area.y + plot_area.height)],
383 &LineStyle {
384 color: major_x.color,
385 width: major_x.width,
386 alpha: 1.0,
387 linetype: Linetype::Solid,
388 },
389 )?;
390 }
391 }
392
393 if major_y.visible {
395 for (pos, _) in &y_breaks {
396 let (_, py) = coord.transform((0.0, *pos), plot_area);
397 backend.draw_line(
398 &[(plot_area.x, py), (plot_area.x + plot_area.width, py)],
399 &LineStyle {
400 color: major_y.color,
401 width: major_y.width,
402 alpha: 1.0,
403 linetype: Linetype::Solid,
404 },
405 )?;
406 }
407 }
408
409 Ok(())
410}