index/objects/plotting/
axes.rs

1use std::rc::Rc;
2
3use wasm_bindgen::prelude::*;
4
5use crate::{objects::{geometry::tipable::Tipable, plotting::function_plotter::ParametricFunctionPlot, vector_object::VectorObjectBuilder}, utils::{interpolation::{inverse_lerp, lerp}, interval::ClosedInterval, point2d::Point2D, style::{Color, Style}}};
6
7/// A Tick is a mark on an axis at a specific value.
8#[wasm_bindgen]
9#[derive(Clone)]
10pub struct Tick {
11    value: f32,
12    label: Option<VectorObjectBuilder>,
13    size: f32,
14    style: Style,
15    stroke_width: f32,
16}
17
18#[wasm_bindgen]
19impl Tick {
20    /// Creates a new Tick with the given value.
21    #[wasm_bindgen(constructor, return_description = "A new tick.")]
22    pub fn new(
23        #[wasm_bindgen(param_description = "The value of the tick.")]
24        value: f32,
25        #[wasm_bindgen(param_description = "The label of the tick.")]
26        label: Option<VectorObjectBuilder>,
27        #[wasm_bindgen(param_description = "The size of the tick.")]
28        size: Option<f32>,
29        #[wasm_bindgen(param_description = "The style of the tick.")]
30        style: Option<Style>,
31        #[wasm_bindgen(param_description = "The stroke width of the tick.")]
32        stroke_width: Option<f32>,
33    ) -> Tick {
34        Tick { value, label, size: size.unwrap_or(10.0), stroke_width: stroke_width.unwrap_or(4.0), style: style.unwrap_or(Style::from_color(Color::new(0, 0, 0, 1.0))) }
35    }
36
37    /// Returns the value of the tick.
38    #[wasm_bindgen(getter, return_description = "The value of the tick.")]
39    pub fn value(&self) -> f32 {
40        self.value
41    }
42
43    /// Returns the label of the tick, if any.
44    #[wasm_bindgen(getter, return_description = "The label of the tick.")]
45    pub fn label(&self) -> Option<VectorObjectBuilder> {
46        match &self.label {
47            Some(label) => Some(label.clone()),
48            None => None,
49        }
50    }
51
52    /// Returns the size of the tick.
53    #[wasm_bindgen(getter, return_description = "The size of the tick.")]
54    pub fn size(&self) -> f32 {
55        self.size
56    }
57
58    /// Returns the style of the tick.
59    #[wasm_bindgen(getter, return_description = "The style of the tick.")]
60    pub fn style(&self) -> Style {
61        self.style.clone()
62    }
63
64    /// Returns the stroke width of the tick.
65    #[wasm_bindgen(getter, return_description = "The stroke width of the tick.")]
66    pub fn stroke_width(&self) -> f32 {
67        self.stroke_width
68    }
69}
70
71/// An Axis for plotting data.
72#[wasm_bindgen]
73#[derive(Clone)]
74pub struct Axis {
75    range: ClosedInterval,
76    render_start: Point2D,
77    render_end: Point2D,
78    label: Option<VectorObjectBuilder>,
79    ticks: Rc<Vec<Tick>>,
80    style: Style,
81    stroke_width: f32,
82}
83
84#[wasm_bindgen]
85impl Axis {
86    /// Creates a new Axis with the given range and label.
87    #[wasm_bindgen(constructor, return_description = "A new axis.")]
88    pub fn new(
89        #[wasm_bindgen(param_description = "The range of the axis.")]
90        range: ClosedInterval,
91        #[wasm_bindgen(param_description = "The minimum coordinates of the axis when rendered.")]
92        render_start: Point2D,
93        #[wasm_bindgen(param_description = "The maximum coordinates of the axis when rendered.")]
94        render_end: Point2D,
95        #[wasm_bindgen(param_description = "The label of the axis.")]
96        label: Option<VectorObjectBuilder>,
97        #[wasm_bindgen(param_description = "The ticks of the axis.")]
98        ticks: Option<Vec<Tick>>,
99        #[wasm_bindgen(param_description = "The style of the axis.")]
100        style: Option<Style>,
101        #[wasm_bindgen(param_description = "The stroke width of the axis.")]
102        stroke_width: Option<f32>,
103    ) -> Axis {
104        let style = style.unwrap_or(Style::from_color(Color::new(0, 0, 0, 1.0)));
105        let stroke_width = stroke_width.unwrap_or(5.0);
106        Axis { range, label, ticks: Rc::new(ticks.unwrap_or_default()), render_start, render_end, style, stroke_width }
107    }
108
109    /// Returns the range of the axis.
110    #[wasm_bindgen(getter, return_description = "The range of the axis.")]
111    pub fn range(&self) -> ClosedInterval {
112        self.range.clone()
113    }
114
115    /// Returns the minimum x-coordinate of the axis when rendered.
116    #[wasm_bindgen(getter, return_description = "The minimum x-coordinate of the axis when rendered.")]
117    pub fn render_start(&self) -> Point2D {
118        self.render_start
119    }
120
121    /// Returns the maximum x-coordinate of the axis when rendered.
122    #[wasm_bindgen(getter, return_description = "The maximum x-coordinate of the axis when rendered.")]
123    pub fn render_end(&self) -> Point2D {
124        self.render_end
125    }
126
127    /// Returns the label of the axis.
128    #[wasm_bindgen(getter, return_description = "The label of the axis.")]
129    pub fn label(&self) -> Option<VectorObjectBuilder> {
130        self.label.clone()
131    }
132
133    /// Returns the ticks of the axis.
134    #[wasm_bindgen(getter, return_description = "The ticks of the axis.")]
135    pub fn ticks(&self) -> Vec<Tick> {
136        self.ticks.to_vec()
137    }
138
139    /// Returns the style of the axis.
140    #[wasm_bindgen(getter, return_description = "The style of the axis.")]
141    pub fn style(&self) -> Style {
142        self.style.clone()
143    }
144
145    /// Returns the number of ticks on the axis.
146    #[wasm_bindgen(getter, return_description = "The number of ticks on the axis.")]
147    pub fn num_ticks(&self) -> usize {
148        self.ticks.len()
149    }
150
151    /// Returns the tick at the given number.
152    #[wasm_bindgen(return_description = "The tick at the given number.")]
153    pub fn tick(
154        &self,
155        #[wasm_bindgen(param_description = "The number coo")]
156        num: f32
157    ) -> Option<Tick> {
158        self.ticks.iter().find(|tick| (tick.value - num).abs() < 0.0001).cloned()
159    }
160
161    /// Gets the coordinates of a point in the axis.
162    #[wasm_bindgen(return_description = "The coordinates of the point in the axis.")]
163    pub fn coord_to_point(&self, num: f32) -> Point2D {
164        let t = inverse_lerp(self.range.start(), self.range.end(), num);
165        Point2D::lerp(&self.render_start, &self.render_end, t)
166    }
167
168    /// Gets the value of a point in the axis.
169    #[wasm_bindgen(return_description = "The value of the point in the axis.")]
170    pub fn point_to_coord(&self, point: Point2D) -> f32 {
171        let t = point.project_onto_line(&self.render_start, &self.render_end);
172        lerp(self.range.start(), self.range.end(), t)
173    }
174
175    /// Gets the VectorObjectBuilder such that when built and rendered, the axis is drawn.
176    #[wasm_bindgen(return_description = "A vector object builder with the axis.")]
177    pub fn vector_object_builder(
178        &self,
179        #[wasm_bindgen(param_description = "The direction of the label. The default is (1, 0).")]
180        label_direction: Option<Point2D>,
181        #[wasm_bindgen(param_description = "The offset of the label. The default is 20.0.")]
182        label_offset: Option<f32>,
183        #[wasm_bindgen(param_description = "The direction of the tick label. The default is (0, 1).")]
184        tick_label_direction: Option<Point2D>,
185        #[wasm_bindgen(param_description = "The offset of the tick label. The default is 10.0.")]
186        tick_label_offset: Option<f32>,
187        #[wasm_bindgen(param_description = "Whether to add the axis label. The default is true.")]
188        add_label: Option<bool>,
189    ) -> VectorObjectBuilder {
190        let mut builder = VectorObjectBuilder::default()
191            .move_point(&self.render_start)
192            .line_to(&self.render_end)
193            .set_stroke(self.style.clone(), None)
194            .set_stroke_width(self.stroke_width, None);
195        for tick in self.ticks.iter() {
196            let point = self.coord_to_point(tick.value());
197            let rotation = (self.render_end - self.render_start).direction();
198            let tick_start = (point + Point2D::new(0.0, -tick.size() / 2.0)).rotate_around(point, rotation);
199            let tick_end = (point + Point2D::new(0.0, tick.size() / 2.0)).rotate_around(point, rotation);
200            let mut child_builder = VectorObjectBuilder::default()
201                .move_point(&tick_start)
202                .line_to(&tick_end)
203                .set_stroke(tick.style.clone(), None)
204                .set_stroke_width(tick.stroke_width, None);
205            if let Some(label) = &tick.label {
206                let the_child_builder = child_builder.clone();
207                let label_direction = tick_label_direction.unwrap_or(Point2D::new(0.0, 1.0));
208                let label_offset = tick_label_offset.unwrap_or(10.0);
209                child_builder = child_builder.add_child(label.clone().next_to_other(the_child_builder, Some(label_direction), Some(label_offset), None, None));
210            }
211            builder = builder.add_child(child_builder.clone());
212        }
213        if add_label.unwrap_or(true) {
214            if let Some(label) = &self.label {
215                let the_builder = builder.clone();
216                let label_direction = label_direction.unwrap_or(Point2D::new(1.0, 0.0));
217                let label_offset = label_offset.unwrap_or(20.0);
218                builder = builder.add_child(label.clone().next_to_other(the_builder, Some(label_direction), Some(label_offset), None, None));
219            }
220        }
221        builder
222    }
223
224    /// Returns the VectorObjectBuilder with the axis and tip at the end.
225    #[wasm_bindgen(return_description = "A VectorObjectBuilder with the axis and tip at the end.")]
226    pub fn with_tip_at_the_end(
227        &self,
228        #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right and centered to (0, 0), this function will rotate and move it to the correct angle.")]
229        tip_shape: VectorObjectBuilder,
230        #[wasm_bindgen(param_description = "The direction of the label. The default is (1, 0).")]
231        label_direction: Option<Point2D>,
232        #[wasm_bindgen(param_description = "The offset of the label. The default is 20.0.")]
233        label_offset: Option<f32>,
234        #[wasm_bindgen(param_description = "The direction of the tick label. The default is (0, 1).")]
235        tick_label_direction: Option<Point2D>,
236        #[wasm_bindgen(param_description = "The offset of the tick label. The default is 10.0.")]
237        tick_label_offset: Option<f32>,
238    ) -> VectorObjectBuilder {
239        let mut builder = self.vector_object_builder(label_direction, label_offset, tick_label_direction, tick_label_offset, Some(false));
240        builder = builder.add_child(self.tip_at_end(tip_shape));
241        if let Some(label) = &self.label {
242            let the_builder = builder.clone();
243            let label_direction = label_direction.unwrap_or(Point2D::new(1.0, 0.0));
244            let label_offset = label_offset.unwrap_or(20.0);
245            builder = builder.add_child(label.clone().next_to_other(the_builder, Some(label_direction), Some(label_offset), None, None));
246        }
247        builder
248    }
249
250    /// Returns the VectorObjectBuilder with the axis and tips at both ends.
251    #[wasm_bindgen(return_description = "A VectorObjectBuilder with the axis and tips at both ends.")]
252    pub fn with_tips_at_both_ends(
253        &self,
254        #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right, this function will rotate and move it to the correct angle.")]
255        tip_shape: VectorObjectBuilder,
256        #[wasm_bindgen(param_description = "The direction of the label. The default is (1, 0).")]
257        label_direction: Option<Point2D>,
258        #[wasm_bindgen(param_description = "The offset of the label. The default is 20.0.")]
259        label_offset: Option<f32>,
260        #[wasm_bindgen(param_description = "The direction of the tick label. The default is (0, 1).")]
261        tick_label_direction: Option<Point2D>,
262        #[wasm_bindgen(param_description = "The offset of the tick label. The default is 10.0.")]
263        tick_label_offset: Option<f32>,
264    ) -> VectorObjectBuilder {
265        let mut builder = self.vector_object_builder(label_direction, label_offset, tick_label_direction, tick_label_offset, Some(false));
266        builder = builder.add_child(self.tip_at_start(tip_shape.clone()));
267        builder = builder.add_child(self.tip_at_end(tip_shape));
268        if let Some(label) = &self.label {
269            let the_builder = builder.clone();
270            let label_direction = label_direction.unwrap_or(Point2D::new(1.0, 0.0));
271            let label_offset = label_offset.unwrap_or(20.0);
272            builder = builder.add_child(label.clone().next_to_other(the_builder, Some(label_direction), Some(label_offset), None, None));
273        }
274        builder
275    }
276
277    /// Clones the axis.
278    #[wasm_bindgen(js_name = clone)]
279    pub fn copy(&self) -> Axis {
280        self.clone()
281    }
282}
283
284impl Tipable for Axis {
285    fn angle_at_end(&self) -> f32 {
286        (self.render_end - self.render_start).direction()
287    }
288
289    fn angle_at_start(&self) -> f32 {
290        (self.render_start - self.render_end).direction()
291    }
292
293    fn end(&self) -> Point2D {
294        self.render_end
295    }
296
297    fn start(&self) -> Point2D {
298        self.render_start
299    }
300}
301
302/// A CartesianAxes represents the axes of a Cartesian plot.
303#[wasm_bindgen]
304#[derive(Clone)]
305pub struct CartesianAxes {
306    x_axis: Axis,
307    y_axis: Axis,
308    coord_to_point: &'static dyn Fn(Point2D) -> Point2D
309}
310
311#[wasm_bindgen]
312impl CartesianAxes {
313    /// Creates a new CartesianAxes with the given x and y axes.
314    #[wasm_bindgen(constructor, return_description = "A new Cartesian axes.")]
315    pub fn new(
316        #[wasm_bindgen(param_description = "The x-range of the Cartesian axes.")]
317        x_range: ClosedInterval,
318        #[wasm_bindgen(param_description = "The y-range of the Cartesian axes.")]
319        y_range: ClosedInterval,
320        #[wasm_bindgen(param_description = "The minimum coordinates of the Cartesian axes when rendered.")]
321        render_start: Point2D,
322        #[wasm_bindgen(param_description = "The maximum coordinates of the Cartesian axes when rendered.")]
323        render_end: Point2D,
324        #[wasm_bindgen(param_description = "The style of each axis.")]
325        style: Option<Style>,
326        #[wasm_bindgen(param_description = "The stroke width of each axis.")]
327        stroke_width: Option<f32>,
328        #[wasm_bindgen(param_description = "The x-axis label.")]
329        x_label: Option<VectorObjectBuilder>,
330        #[wasm_bindgen(param_description = "The y-axis label.")]
331        y_label: Option<VectorObjectBuilder>,
332        #[wasm_bindgen(param_description = "The x-axis ticks.")]
333        x_ticks: Option<Vec<Tick>>,
334        #[wasm_bindgen(param_description = "The y-axis ticks.")]
335        y_ticks: Option<Vec<Tick>>,
336    ) -> CartesianAxes {
337        let x_ticks = match x_ticks {
338            Some(ticks) => Rc::new(ticks),
339            None => Rc::new(vec![]),
340        };
341        let y_ticks = match y_ticks {
342            Some(ticks) => Rc::new(ticks),
343            None => Rc::new(vec![]),
344        };
345        let mut x_axis = Box::new(Axis::new(x_range.clone(), render_start, render_end, x_label.clone(), Some(x_ticks.to_vec()), style.clone(), stroke_width));
346        let mut y_axis = Box::new(Axis::new(y_range.clone(), render_start, render_end, y_label.clone(), Some(y_ticks.to_vec()), style.clone(), stroke_width));
347        if y_range.start() > 0.0 {
348            x_axis = Box::new(Axis::new(
349                x_range.clone(),
350                Point2D::new(render_start.x, x_axis.coord_to_point(y_range.start()).y),
351                Point2D::new(render_end.x, x_axis.coord_to_point(y_range.start()).y),
352                x_label.clone(),
353                Some(x_ticks.to_vec()),
354                style.clone(),
355                stroke_width
356            ));
357        } else if y_range.end() < 0.0 {
358            x_axis = Box::new(Axis::new(
359                x_range.clone(),
360                Point2D::new(render_start.x, x_axis.coord_to_point(y_range.end()).y),
361                Point2D::new(render_end.x, x_axis.coord_to_point(y_range.end()).y),
362                x_label.clone(),
363                Some(x_ticks.to_vec()),
364                style.clone(),
365                stroke_width
366            ));
367        } else {
368            x_axis = Box::new(Axis::new(
369                x_range.clone(),
370                Point2D::new(render_start.x, x_axis.coord_to_point(0.0).y),
371                Point2D::new(render_end.x, x_axis.coord_to_point(0.0).y),
372                x_label.clone(),
373                Some(x_ticks.to_vec()),
374                style.clone(),
375                stroke_width
376            ));
377        }
378        if x_range.start() > 0.0 {
379            y_axis = Box::new(Axis::new(
380                y_range.clone(),
381                Point2D::new(y_axis.coord_to_point(x_range.start()).x, render_start.y),
382                Point2D::new(y_axis.coord_to_point(x_range.start()).x, render_end.y),
383                y_label.clone(),
384                Some(y_ticks.to_vec()),
385                style.clone(),
386                stroke_width
387            ));
388        } else if x_range.end() < 0.0 {
389            y_axis = Box::new(Axis::new(
390                y_range.clone(),
391                Point2D::new(y_axis.coord_to_point(x_range.end()).x, render_start.y),
392                Point2D::new(y_axis.coord_to_point(x_range.end()).x, render_end.y),
393                y_label.clone(),
394                Some(y_ticks.to_vec()),
395                style.clone(),
396                stroke_width
397            ));
398        } else {
399            y_axis = Box::new(Axis::new(
400                y_range.clone(),
401                Point2D::new(y_axis.coord_to_point(0.0).x, render_start.y),
402                Point2D::new(y_axis.coord_to_point(0.0).x, render_end.y),
403                y_label.clone(),
404                Some(y_ticks.to_vec()),
405                style.clone(),
406                stroke_width
407            ));
408        }
409        let the_x_axis = Box::leak(x_axis.clone());
410        let the_y_axis = Box::leak(y_axis.clone());
411        CartesianAxes {
412            x_axis: the_x_axis.clone(),
413            y_axis: the_y_axis.clone(),
414            coord_to_point: Box::leak(Box::new(|point: Point2D| -> Point2D {
415                Point2D::new(the_x_axis.coord_to_point(point.x).x, the_y_axis.coord_to_point(point.y).y)
416            }))
417        }
418    }
419
420    /// Returns the x-axis of the Cartesian axes.
421    #[wasm_bindgen(getter, return_description = "The x-axis of the Cartesian axes.")]
422    pub fn x_axis(&self) -> Axis {
423        self.x_axis.clone()
424    }
425
426    /// Returns the y-axis of the Cartesian axes.
427    #[wasm_bindgen(getter, return_description = "The y-axis of the Cartesian axes.")]
428    pub fn y_axis(&self) -> Axis {
429        self.y_axis.clone()
430    }
431
432    /// Gets the VectorObjectBuilder such that when built and rendered, the Cartesian axes are drawn.
433    #[wasm_bindgen(return_description = "A vector object builder with the Cartesian axes.")]
434    pub fn vector_object_builder(
435        &self,
436        #[wasm_bindgen(param_description = "The direction of the x-label. The default is (1, 0).")]
437        x_label_direction: Option<Point2D>,
438        #[wasm_bindgen(param_description = "The offset of the x-label. The default is 20.0.")]
439        x_label_offset: Option<f32>,
440        #[wasm_bindgen(param_description = "The direction of the x-tick label. The default is (0, 1).")]
441        x_tick_label_direction: Option<Point2D>,
442        #[wasm_bindgen(param_description = "The offset of the x-tick label. The default is 10.0.")]
443        x_tick_label_offset: Option<f32>,
444        #[wasm_bindgen(param_description = "The direction of the y-label. The default is (1, 0).")]
445        y_label_direction: Option<Point2D>,
446        #[wasm_bindgen(param_description = "The offset of the y-label. The default is 20.0.")]
447        y_label_offset: Option<f32>,
448        #[wasm_bindgen(param_description = "The direction of the y-tick label. The default is (0, 1).")]
449        y_tick_label_direction: Option<Point2D>,
450        #[wasm_bindgen(param_description = "The offset of the y-tick label. The default is 10.0.")]
451        y_tick_label_offset: Option<f32>,
452    ) -> VectorObjectBuilder {
453        let mut builder = VectorObjectBuilder::default();
454        builder = builder.add_child(self.x_axis.vector_object_builder(x_label_direction, x_label_offset, x_tick_label_direction, x_tick_label_offset, None));
455        builder = builder.add_child(self.y_axis.vector_object_builder(y_label_direction, y_label_offset, y_tick_label_direction, y_tick_label_offset, None));
456        builder
457    }
458
459
460    /// Returns the VectorObjectBuilder with the Cartesian axes and tip at the end of both axes.
461    #[wasm_bindgen(return_description = "A VectorObjectBuilder with the Cartesian axes and tip at the end of both axes.")]
462    pub fn with_tips_at_ends(
463        &self,
464        #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right, this function will rotate and move it to the correct angle.")]
465        tip_shape: VectorObjectBuilder,
466        #[wasm_bindgen(param_description = "The direction of the x-label. The default is (1, 0).")]
467        x_label_direction: Option<Point2D>,
468        #[wasm_bindgen(param_description = "The offset of the x-label. The default is 20.0.")]
469        x_label_offset: Option<f32>,
470        #[wasm_bindgen(param_description = "The direction of the x-tick label. The default is (0, 1).")]
471        x_tick_label_direction: Option<Point2D>,
472        #[wasm_bindgen(param_description = "The offset of the x-tick label. The default is 10.0.")]
473        x_tick_label_offset: Option<f32>,
474        #[wasm_bindgen(param_description = "The direction of the y-label. The default is (1, 0).")]
475        y_label_direction: Option<Point2D>,
476        #[wasm_bindgen(param_description = "The offset of the y-label. The default is 20.0.")]
477        y_label_offset: Option<f32>,
478        #[wasm_bindgen(param_description = "The direction of the y-tick label. The default is (0, 1).")]
479        y_tick_label_direction: Option<Point2D>,
480        #[wasm_bindgen(param_description = "The offset of the y-tick label. The default is 10.0.")]
481        y_tick_label_offset: Option<f32>,
482    ) -> VectorObjectBuilder {
483        let mut builder = VectorObjectBuilder::default();
484        let y_label_direction = y_label_direction.unwrap_or(Point2D::new(0.0, -1.0));
485        let y_tick_label_direction = y_tick_label_direction.unwrap_or(Point2D::new(-1.0, 0.0));
486        builder = builder.add_child(self.x_axis.with_tip_at_the_end(tip_shape.clone(), x_label_direction, x_label_offset, x_tick_label_direction, x_tick_label_offset));
487        builder = builder.add_child(self.y_axis.with_tip_at_the_end(tip_shape, Some(y_label_direction), y_label_offset, Some(y_tick_label_direction), y_tick_label_offset));
488        builder
489    }
490
491    /// Returns the VectorObjectBuilder with the Cartesian axes and tips at both ends of both axes.
492    #[wasm_bindgen(return_description = "A VectorObjectBuilder with the Cartesian axes and tips at both ends of both axes.")]
493    pub fn with_tips_at_both_ends(
494        &self,
495        #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right, this function will rotate and move it to the correct angle.")]
496        tip_shape: VectorObjectBuilder,
497        #[wasm_bindgen(param_description = "The direction of the x-label. The default is (1, 0).")]
498        x_label_direction: Option<Point2D>,
499        #[wasm_bindgen(param_description = "The offset of the x-label. The default is 20.0.")]
500        x_label_offset: Option<f32>,
501        #[wasm_bindgen(param_description = "The direction of the x-tick label. The default is (0, 1).")]
502        x_tick_label_direction: Option<Point2D>,
503        #[wasm_bindgen(param_description = "The offset of the x-tick label. The default is 10.0.")]
504        x_tick_label_offset: Option<f32>,
505        #[wasm_bindgen(param_description = "The direction of the y-label. The default is (1, 0).")]
506        y_label_direction: Option<Point2D>,
507        #[wasm_bindgen(param_description = "The offset of the y-label. The default is 20.0.")]
508        y_label_offset: Option<f32>,
509        #[wasm_bindgen(param_description = "The direction of the y-tick label. The default is (0, 1).")]
510        y_tick_label_direction: Option<Point2D>,
511        #[wasm_bindgen(param_description = "The offset of the y-tick label. The default is 10.0.")]
512        y_tick_label_offset: Option<f32>,
513    ) -> VectorObjectBuilder {
514        let mut builder = VectorObjectBuilder::default();
515        builder = builder.add_child(self.x_axis.with_tips_at_both_ends(tip_shape.clone(), x_label_direction, x_label_offset, x_tick_label_direction, x_tick_label_offset));
516        builder = builder.add_child(self.y_axis.with_tips_at_both_ends(tip_shape, y_label_direction, y_label_offset, y_tick_label_direction, y_tick_label_offset));
517        builder
518    }
519
520    /// Returns the associated point of the given coordinates in the Cartesian axes.
521    #[wasm_bindgen(return_description = "The associated point of the given coordinates in the Cartesian axes.")]
522    pub fn coord_to_point(&self, p: Point2D) -> Point2D {
523        Point2D::new(self.x_axis.coord_to_point(p.x).x, self.y_axis.coord_to_point(p.y).y)
524    }
525
526    /// Returns the associated coordinates of the given point in the Cartesian axes.
527    #[wasm_bindgen(return_description = "The associated coordinates of the given point in the Cartesian axes.")]
528    pub fn point_to_coord(&self, point: Point2D) -> Point2D {
529        Point2D::new(self.x_axis.point_to_coord(Point2D::new(point.x, 0.0)), self.y_axis.point_to_coord(Point2D::new(0.0, point.y)))
530    }
531
532    /// Returns the ParametricFunctionPlot object with the given function, relative to the Cartesian axes' coordinates.
533    #[wasm_bindgen(return_description = "The ParametricFunctionPlot object with the given function.")]
534    pub fn plot_function(
535        &self,
536        #[wasm_bindgen(param_description = "The x function to plot.")]
537        expression_x: String,
538        #[wasm_bindgen(param_description = "The y function to plot.")]
539        expression_y: String,
540        #[wasm_bindgen(param_description = "The domain of the parametric function.")]
541        domain: ClosedInterval,
542        #[wasm_bindgen(param_description = "The x-range of the plot.")]
543        x_range: ClosedInterval,
544        #[wasm_bindgen(param_description = "The y-range of the plot.")]
545        y_range: ClosedInterval,
546        #[wasm_bindgen(param_description = "The discontinuities of the plot.", unchecked_param_type = "number[]")]
547        discontinuities: Option<Vec<f32>>,
548        #[wasm_bindgen(param_description = "The minimum depth of the plot.")]
549        min_depth: Option<u32>,
550        #[wasm_bindgen(param_description = "The maximum depth of the plot.")]
551        max_depth: Option<u32>,
552        #[wasm_bindgen(param_description = "The threshold of the plot.")]
553        threshold: Option<f32>,
554    ) -> Result<ParametricFunctionPlot, JsError> {
555        let mut plot = ParametricFunctionPlot::new(
556            expression_x,
557            expression_y,
558            domain,
559            x_range,
560            y_range,
561            discontinuities,
562            min_depth,
563            max_depth,
564            threshold,
565        )?;
566        plot.compose(Box::new(self.coord_to_point));
567        Ok(plot)
568    }
569
570    /// Clones the Cartesian axes.
571    #[wasm_bindgen(js_name = clone)]
572    pub fn copy(&self) -> CartesianAxes {
573        self.clone()
574    }
575}