Skip to main content

euv_chart/model/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`Scale`].
4impl Scale {
5    /// Returns a linear scale covering the given domain and range.
6    ///
7    /// # Arguments
8    ///
9    /// - `f64` - A 64-bit float (`f64`).
10    pub fn linear(domain_min: f64, domain_max: f64, range_min: f64, range_max: f64) -> Self {
11        Self {
12            kind: ScaleKind::Linear,
13            domain_min,
14            domain_max,
15            range_min,
16            range_max,
17            categories: Vec::new(),
18            log_base: 10.0,
19            band: 0.0,
20            count: 0,
21        }
22    }
23
24    /// Returns a logarithmic scale covering the given domain and range.
25    ///
26    /// # Arguments
27    ///
28    /// - `f64` - A 64-bit float (`f64`).
29    pub fn log(
30        domain_min: f64,
31        domain_max: f64,
32        range_min: f64,
33        range_max: f64,
34        base: f64,
35    ) -> Self {
36        Self {
37            kind: ScaleKind::Log,
38            domain_min,
39            domain_max,
40            range_min,
41            range_max,
42            categories: Vec::new(),
43            log_base: base,
44            band: 0.0,
45            count: 0,
46        }
47    }
48
49    /// Returns an ordinal scale over the given categories.
50    ///
51    /// `range_min` and `range_max` are the positions of the *centres* of
52    /// the first and the last band, and `band` is the width of one band.
53    ///
54    /// # Arguments
55    ///
56    /// - `Vec<String>` - A `Vec<String>` parameter.
57    /// - `f64` - A 64-bit float (`f64`).
58    pub fn category(categories: Vec<String>, range_min: f64, range_max: f64, band: f64) -> Self {
59        let count: usize = categories.len();
60        Self {
61            kind: ScaleKind::Category,
62            domain_min: 0.0,
63            domain_max: count.saturating_sub(1) as f64,
64            range_min,
65            range_max,
66            categories,
67            log_base: 10.0,
68            band,
69            count,
70        }
71    }
72
73    /// Returns an ordinal scale over the given number of categories.
74    ///
75    /// The scale has no names: it is used for a chart whose points are
76    /// not named, where keeping a million empty strings would be pure
77    /// waste.
78    ///
79    /// # Arguments
80    ///
81    /// - `usize` - A non-negative integer (`usize`).
82    /// - `f64` - A 64-bit float (`f64`).
83    pub fn category_count(count: usize, range_min: f64, range_max: f64, band: f64) -> Self {
84        Self {
85            kind: ScaleKind::Category,
86            domain_min: 0.0,
87            domain_max: count.saturating_sub(1) as f64,
88            range_min,
89            range_max,
90            categories: Vec::new(),
91            log_base: 10.0,
92            band,
93            count,
94        }
95    }
96
97    /// Maps a data value to a pixel position.
98    ///
99    /// Values outside the domain are clamped to the range, which is what
100    /// keeps a series that overflows its axis from drawing outside the
101    /// grid.
102    ///
103    /// # Arguments
104    ///
105    /// - `f64` - A 64-bit float (`f64`).
106    ///
107    /// # Returns
108    ///
109    /// - `f64` - A 64-bit float.
110    pub fn map(&self, value: f64) -> f64 {
111        match self.get_kind() {
112            ScaleKind::Category => self.map_ordinal(value),
113            ScaleKind::Log => {
114                let base: f64 = self.get_log_base().max(1.000_001);
115                let low: f64 = self.get_domain_min().max(f64::MIN_POSITIVE).log(base);
116                let high: f64 = self.get_domain_max().max(f64::MIN_POSITIVE).log(base);
117                let position: f64 = if (high - low).abs() < f64::EPSILON {
118                    self.get_range_min()
119                } else {
120                    let t: f64 = (value.max(f64::MIN_POSITIVE).log(base) - low) / (high - low);
121                    self.get_range_min() + t * (self.get_range_max() - self.get_range_min())
122                };
123                position.clamp(
124                    self.get_range_min().min(self.get_range_max()),
125                    self.get_range_max().max(self.get_range_min()),
126                )
127            }
128            _ => {
129                let span: f64 = self.get_domain_max() - self.get_domain_min();
130                if span.abs() < f64::EPSILON {
131                    return (self.get_range_min() + self.get_range_max()) * 0.5;
132                }
133                let t: f64 = (value - self.get_domain_min()) / span;
134                let position: f64 =
135                    self.get_range_min() + t * (self.get_range_max() - self.get_range_min());
136                position.clamp(
137                    self.get_range_min().min(self.get_range_max()),
138                    self.get_range_max().max(self.get_range_min()),
139                )
140            }
141        }
142    }
143
144    /// Maps an ordinal index to a pixel position.
145    ///
146    /// # Arguments
147    ///
148    /// - `f64` - A 64-bit float (`f64`).
149    ///
150    /// # Returns
151    ///
152    /// - `f64` - A 64-bit float.
153    fn map_ordinal(&self, value: f64) -> f64 {
154        if self.get_count() <= 1 {
155            return (self.get_range_min() + self.get_range_max()) * 0.5;
156        }
157        let span: f64 = self.get_domain_max() - self.get_domain_min();
158        if span.abs() < f64::EPSILON {
159            return self.get_range_min();
160        }
161        let t: f64 = (value - self.get_domain_min()) / span;
162        let position: f64 =
163            self.get_range_min() + t * (self.get_range_max() - self.get_range_min());
164        position.clamp(
165            self.get_range_min().min(self.get_range_max()),
166            self.get_range_max().max(self.get_range_min()),
167        )
168    }
169
170    /// Maps a pixel position back to a data value.
171    ///
172    /// # Arguments
173    ///
174    /// - `f64` - A 64-bit float (`f64`).
175    ///
176    /// # Returns
177    ///
178    /// - `f64` - A 64-bit float.
179    pub fn invert(&self, pixel: f64) -> f64 {
180        match self.get_kind() {
181            ScaleKind::Log => {
182                let base: f64 = self.get_log_base().max(1.000_001);
183                let span: f64 = self.get_range_max() - self.get_range_min();
184                if span.abs() < f64::EPSILON {
185                    return self.get_domain_min();
186                }
187                let t: f64 = (pixel - self.get_range_min()) / span;
188                let low: f64 = self.get_domain_min().max(f64::MIN_POSITIVE).log(base);
189                let high: f64 = self.get_domain_max().max(f64::MIN_POSITIVE).log(base);
190                (low + t * (high - low)).powf(base)
191            }
192            _ => {
193                let span: f64 = self.get_range_max() - self.get_range_min();
194                if span.abs() < f64::EPSILON {
195                    return self.get_domain_min();
196                }
197                let t: f64 = (pixel - self.get_range_min()) / span;
198                self.get_domain_min() + t * (self.get_domain_max() - self.get_domain_min())
199            }
200        }
201    }
202
203    /// Returns the width of one band of an ordinal scale, in pixels.
204    ///
205    /// Returns `0.0` for a scale that is not ordinal.
206    ///
207    /// # Returns
208    ///
209    /// - `f64` - A 64-bit float.
210    pub fn band_width(&self) -> f64 {
211        if self.get_kind() != ScaleKind::Category {
212            return 0.0;
213        }
214        self.get_band()
215    }
216
217    /// Returns `true` when the scale maps ordinal values.
218    ///
219    /// # Returns
220    ///
221    /// - `bool` - A boolean.
222    pub fn is_ordinal(&self) -> bool {
223        self.get_kind() == ScaleKind::Category
224    }
225}
226
227/// Inherent implementation of [`AxisLayout`].
228impl AxisLayout {
229    /// Returns the pixel position of the given data value.
230    ///
231    /// # Arguments
232    ///
233    /// - `f64` - A 64-bit float (`f64`).
234    ///
235    /// # Returns
236    ///
237    /// - `f64` - A 64-bit float.
238    pub fn position_of(&self, value: f64) -> f64 {
239        self.get_scale().map(value)
240    }
241
242    /// Returns the index of the category at the given pixel position.
243    ///
244    /// # Arguments
245    ///
246    /// - `f64` - A 64-bit float (`f64`).
247    ///
248    /// # Returns
249    ///
250    /// - `Option<usize>` - `Some(...)` on success, `None` otherwise.
251    pub fn category_at(&self, pixel: f64) -> Option<usize> {
252        if !self.get_scale().is_ordinal() {
253            return None;
254        }
255        let value: f64 = self.get_scale().invert(pixel);
256        let index: isize = value.round() as isize;
257        // The count is the number of categories whether they are named
258        // or not: a chart over a million unnamed points has a million
259        // categories and not one name.
260        let count: usize = if self.get_scale().get_categories().is_empty() {
261            self.get_scale().get_count()
262        } else {
263            self.get_scale().get_categories().len()
264        };
265        if index < 0 || index as usize >= count {
266            return None;
267        }
268        Some(index as usize)
269    }
270}
271
272/// Inherent implementation of [`GridLayout`].
273impl GridLayout {
274    /// Returns the x-axis at the given index, falling back to the first
275    /// one.
276    ///
277    /// # Arguments
278    ///
279    /// - `usize` - A non-negative integer (`usize`).
280    ///
281    /// # Returns
282    ///
283    /// - `Option<&AxisLayout>` - `Some(...)` on success, `None` otherwise.
284    pub fn x_axis(&self, index: usize) -> Option<&AxisLayout> {
285        self.get_x_axes()
286            .get(index)
287            .or_else(|| self.get_x_axes().first())
288    }
289
290    /// Returns the y-axis at the given index, falling back to the first
291    /// one.
292    ///
293    /// # Arguments
294    ///
295    /// - `usize` - A non-negative integer (`usize`).
296    ///
297    /// # Returns
298    ///
299    /// - `Option<&AxisLayout>` - `Some(...)` on success, `None` otherwise.
300    pub fn y_axis(&self, index: usize) -> Option<&AxisLayout> {
301        self.get_y_axes()
302            .get(index)
303            .or_else(|| self.get_y_axes().first())
304    }
305}
306
307/// Inherent implementation of [`ChartLayout`].
308impl ChartLayout {
309    /// Computes the layout of a chart.
310    ///
311    /// The layout is the bridge between the option and the display list:
312    /// it resolves the sizes, computes the scales and the ticks, and
313    /// places every data point in pixel space. The renderer then only has
314    /// to turn the result into drawing instructions.
315    ///
316    /// `measurer` is used to size the axis labels; passing a measurer
317    /// that has already measured the same labels costs nothing, because
318    /// the measurements are cached.
319    ///
320    /// # Arguments
321    ///
322    /// - `&ChartOption` - Shared reference to a `ChartOption`.
323    /// - `f64` - A 64-bit float (`f64`).
324    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
325    ///
326    /// # Returns
327    ///
328    /// - `ChartLayout` - A `ChartLayout` value.
329    pub fn compute(
330        option: &ChartOption,
331        width: f64,
332        height: f64,
333        measurer: &TextMeasurer,
334    ) -> ChartLayout {
335        Self::compute_with_maps(option, width, height, measurer, &MapRegistry::new())
336    }
337
338    /// Computes the layout of a chart that draws maps.
339    ///
340    /// The geometry of a map is far too large to live in an option: the
341    /// same map is drawn by every chart of an application, and it is
342    /// registered once. The registry is where the charts look it up.
343    ///
344    /// # Arguments
345    ///
346    /// - `&ChartOption` - Shared reference to a `ChartOption`.
347    /// - `f64` - A 64-bit float (`f64`).
348    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
349    /// - `&MapRegistry` - Shared reference to a `MapRegistry`.
350    ///
351    /// # Returns
352    ///
353    /// - `ChartLayout` - A `ChartLayout` value.
354    pub fn compute_with_maps(
355        option: &ChartOption,
356        width: f64,
357        height: f64,
358        measurer: &TextMeasurer,
359        maps: &MapRegistry,
360    ) -> ChartLayout {
361        let mut layout: ChartLayout = ChartLayout {
362            width,
363            height,
364            grids: Vec::new(),
365            series: Vec::new(),
366            geo: Vec::new(),
367            title_rect: None,
368            legend_rect: None,
369            plot_rect: BBox::from_min_size(0.0, 0.0, width, height),
370        };
371
372        // The title and the legend take space away from the plot area.
373        let title_space: f64 = layout.measure_title(option, measurer);
374        let legend_space: f64 = layout.measure_legend(option, measurer);
375
376        layout.plot_rect = BBox::from_min_size(
377            0.0,
378            title_space,
379            width,
380            (height - title_space - legend_space).max(0.0),
381        );
382
383        layout.grids = layout.measure_grids(option, &layout.plot_rect, measurer);
384        layout.series = layout.measure_series(option, &layout.grids);
385        layout.geo = layout.measure_geo(option, maps);
386        layout
387    }
388
389    /// Measures the space the title needs.
390    ///
391    /// # Arguments
392    ///
393    /// - `&ChartOption` - Shared reference to a `ChartOption`.
394    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
395    ///
396    /// # Returns
397    ///
398    /// - `f64` - A 64-bit float.
399    fn measure_title(&mut self, option: &ChartOption, measurer: &TextMeasurer) -> f64 {
400        let Some(title) = &option.title else {
401            return 0.0;
402        };
403        if !title.show || title.text.is_empty() {
404            return 0.0;
405        }
406        let mut space: f64 = 0.0;
407        let font_size: f64 = title
408            .text_style
409            .as_ref()
410            .and_then(|style: &LabelOption| style.font_size)
411            .unwrap_or(DEFAULT_TITLE_FONT_SIZE);
412        let metrics: TextMetrics = measurer.measure(&title.text, &font_string(font_size));
413        space += metrics.height;
414        if !title.subtext.is_empty() {
415            let subtext_size: f64 = title
416                .subtext_style
417                .as_ref()
418                .and_then(|style: &LabelOption| style.font_size)
419                .unwrap_or(DEFAULT_SUBTITLE_FONT_SIZE);
420            let subtext: TextMetrics = measurer.measure(&title.subtext, &font_string(subtext_size));
421            space += subtext.height + title.item_gap.unwrap_or(4.0);
422        }
423        space += 8.0;
424        self.set_title_rect(Some(BBox::from_min_size(0.0, 0.0, self.get_width(), space)));
425        space
426    }
427
428    /// Measures the space the legend needs.
429    ///
430    /// # Arguments
431    ///
432    /// - `&ChartOption` - Shared reference to a `ChartOption`.
433    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
434    ///
435    /// # Returns
436    ///
437    /// - `f64` - A 64-bit float.
438    fn measure_legend(&mut self, option: &ChartOption, measurer: &TextMeasurer) -> f64 {
439        let Some(legend) = &option.legend else {
440            return 0.0;
441        };
442        if !legend.show {
443            return 0.0;
444        }
445        let font_size: f64 = legend
446            .text_style
447            .as_ref()
448            .and_then(|style: &LabelOption| style.font_size)
449            .unwrap_or(DEFAULT_LEGEND_FONT_SIZE);
450        let mut widest: f64 = 0.0_f64;
451        for name in &legend.data {
452            let metrics: TextMetrics = measurer.measure(name, &font_string(font_size));
453            widest = widest.max(metrics.width);
454        }
455        let item_width: f64 = legend.item_width.unwrap_or(25.0);
456        let item_gap: f64 = legend.item_gap.unwrap_or(10.0);
457        let item_height: f64 = legend.item_height.unwrap_or(14.0);
458        let space: f64 = (item_height.max(font_size) + 8.0).min(self.get_height() * 0.5);
459        self.set_legend_rect(Some(BBox::from_min_size(
460            0.0,
461            0.0,
462            widest + item_width + item_gap,
463            space,
464        )));
465        space
466    }
467
468    /// Computes the layout of every grid of the chart.
469    ///
470    /// # Arguments
471    ///
472    /// - `&ChartOption` - Shared reference to a `ChartOption`.
473    /// - `&BBox` - Shared reference to a `BBox`.
474    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
475    ///
476    /// # Returns
477    ///
478    /// - `Vec<GridLayout>` - A `Vec<GridLayout>` value.
479    fn measure_grids(
480        &self,
481        option: &ChartOption,
482        plot: &BBox,
483        measurer: &TextMeasurer,
484    ) -> Vec<GridLayout> {
485        let grid_option: GridOption = option.grid.clone().unwrap_or_default();
486        let mut grids: Vec<GridLayout> = Vec::new();
487
488        // A chart without an explicit grid still gets one: the cartesian
489        // series need somewhere to live.
490        let count: usize = 1;
491        for index in 0..count {
492            let rect: BBox = self.resolve_grid_rect(&grid_option, plot);
493            let mut x_axes: Vec<AxisLayout> = Vec::new();
494            let mut y_axes: Vec<AxisLayout> = Vec::new();
495
496            for (axis_index, axis) in option.x_axis.iter().enumerate() {
497                if axis.grid_index.unwrap_or(0) != index {
498                    continue;
499                }
500                x_axes.push(self.measure_x_axis(axis, axis_index, &rect, &option.series, measurer));
501            }
502            for (axis_index, axis) in option.y_axis.iter().enumerate() {
503                if axis.grid_index.unwrap_or(0) != index {
504                    continue;
505                }
506                y_axes.push(self.measure_y_axis(axis, axis_index, &rect, &option.series, measurer));
507            }
508
509            // Without an explicit axis, the chart falls back to a value
510            // axis on the left and a category axis on the bottom.
511            if x_axes.is_empty() {
512                x_axes.push(self.default_x_axis(&rect, &option.series, measurer));
513            }
514            if y_axes.is_empty() {
515                y_axes.push(self.default_y_axis(&rect, &option.series));
516            }
517
518            grids.push(GridLayout {
519                index,
520                rect,
521                x_axes,
522                y_axes,
523            });
524        }
525        grids
526    }
527
528    /// Projects the maps of the chart into the plot area.
529    ///
530    /// A map is projected once, however many series draw it: the
531    /// projection is the expensive part, and every series that draws
532    /// the same map shares it.
533    ///
534    /// # Arguments
535    ///
536    /// - `&ChartOption` - Shared reference to a `ChartOption`.
537    /// - `&MapRegistry` - Shared reference to a `MapRegistry`.
538    ///
539    /// # Returns
540    ///
541    /// - `Vec<GeoLayout>` - A `Vec<GeoLayout>` value.
542    fn measure_geo(&self, option: &ChartOption, maps: &MapRegistry) -> Vec<GeoLayout> {
543        let mut names: Vec<(String, f64)> = Vec::new();
544        let remember: fn(&mut Vec<(String, f64)>, &str, f64) =
545            |names: &mut Vec<(String, f64)>, name: &str, aspect: f64| {
546                if !names.iter().any(|(known, _): &(String, f64)| known == name) {
547                    names.push((name.to_string(), aspect));
548                }
549            };
550        for geo in &option.geo {
551            let Some(name) = &geo.map_name else {
552                continue;
553            };
554            remember(
555                &mut names,
556                name,
557                geo.aspect_scale.unwrap_or(DEFAULT_ASPECT_SCALE),
558            );
559        }
560        for series in &option.series {
561            if series.kind != SeriesType::Map {
562                continue;
563            }
564            let Some(name) = map_series_name(series, option) else {
565                continue;
566            };
567            remember(&mut names, &name, map_series_aspect(series, option));
568        }
569        let mut layouts: Vec<GeoLayout> = Vec::new();
570        for (name, aspect) in names {
571            let Some(data) = maps.get(&name) else {
572                continue;
573            };
574            layouts.push(GeoLayout::compute(data, self.get_plot_rect(), aspect));
575        }
576        layouts
577    }
578
579    /// Resolves the rectangle of the grid.
580    ///
581    /// # Arguments
582    ///
583    /// - `&GridOption` - Shared reference to a `GridOption`.
584    /// - `&BBox` - Shared reference to a `BBox`.
585    ///
586    /// # Returns
587    ///
588    /// - `BBox` - A `BBox` value.
589    fn resolve_grid_rect(&self, option: &GridOption, plot: &BBox) -> BBox {
590        let left: f64 = resolve_edge(
591            option.left.as_ref(),
592            DEFAULT_GRID_LEFT * plot.width(),
593            plot.width(),
594        );
595        let right: f64 = resolve_edge(
596            option.right.as_ref(),
597            DEFAULT_GRID_RIGHT * plot.width(),
598            plot.width(),
599        );
600        let top: f64 = resolve_edge(option.top.as_ref(), DEFAULT_GRID_TOP, plot.height());
601        let bottom: f64 = resolve_edge(option.bottom.as_ref(), DEFAULT_GRID_BOTTOM, plot.height());
602        let min_x: f64 = plot.min_x + left;
603        let min_y: f64 = plot.min_y + top;
604        let max_x: f64 = plot.max_x - right;
605        let max_y: f64 = plot.max_y - bottom;
606        BBox::from_corners(min_x, min_y, max_x.max(min_x), max_y.max(min_y))
607    }
608
609    /// Measures one x-axis.
610    ///
611    /// # Arguments
612    ///
613    /// - `&AxisOption` - Shared reference to a `AxisOption`.
614    /// - `usize` - A non-negative integer (`usize`).
615    /// - `&BBox` - Shared reference to a `BBox`.
616    /// - `&[SeriesOption]` - Shared reference to a `[SeriesOption]`.
617    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
618    ///
619    /// # Returns
620    ///
621    /// - `AxisLayout` - A `AxisLayout` value.
622    fn measure_x_axis(
623        &self,
624        axis: &AxisOption,
625        index: usize,
626        rect: &BBox,
627        series: &[SeriesOption],
628        measurer: &TextMeasurer,
629    ) -> AxisLayout {
630        let side: AxisSide = match axis.position {
631            Some(Position::Top) => AxisSide::Top,
632            _ => AxisSide::Bottom,
633        };
634        let offset: f64 = match side {
635            AxisSide::Top => rect.min_y,
636            _ => rect.max_y,
637        };
638        let (scale, ticks) = self.build_scale(axis, ScaleKind::Linear, rect, series, true);
639        let (label_space, _, label_interval) =
640            self.measure_label_space(&ticks, axis, measurer, false, rect.width());
641        let name_space: f64 = if axis.name.is_some() { 20.0 } else { 0.0 };
642        AxisLayout {
643            index,
644            kind: axis.axis_type,
645            scale,
646            ticks,
647            side,
648            offset,
649            label_space,
650            name: axis.name.clone(),
651            name_space,
652            show: axis.show,
653            labels: Vec::new(),
654            label_interval,
655        }
656    }
657
658    /// Measures one y-axis.
659    ///
660    /// # Arguments
661    ///
662    /// - `&AxisOption` - Shared reference to a `AxisOption`.
663    /// - `usize` - A non-negative integer (`usize`).
664    /// - `&BBox` - Shared reference to a `BBox`.
665    /// - `&[SeriesOption]` - Shared reference to a `[SeriesOption]`.
666    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
667    ///
668    /// # Returns
669    ///
670    /// - `AxisLayout` - A `AxisLayout` value.
671    fn measure_y_axis(
672        &self,
673        axis: &AxisOption,
674        index: usize,
675        rect: &BBox,
676        series: &[SeriesOption],
677        measurer: &TextMeasurer,
678    ) -> AxisLayout {
679        let side: AxisSide = match axis.position {
680            Some(Position::Right) => AxisSide::Right,
681            _ => AxisSide::Left,
682        };
683        let offset: f64 = match side {
684            AxisSide::Right => rect.max_x,
685            _ => rect.min_x,
686        };
687        let (scale, ticks) = self.build_scale(axis, ScaleKind::Linear, rect, series, false);
688        let (label_space, _, label_interval) =
689            self.measure_label_space(&ticks, axis, measurer, true, rect.height());
690        let name_space: f64 = if axis.name.is_some() { 20.0 } else { 0.0 };
691        AxisLayout {
692            index,
693            kind: axis.axis_type,
694            scale,
695            ticks,
696            side,
697            offset,
698            label_space,
699            name: axis.name.clone(),
700            name_space,
701            show: axis.show,
702            labels: Vec::new(),
703            label_interval,
704        }
705    }
706
707    /// Builds the default x-axis of a chart without explicit axes.
708    ///
709    /// The axis behaves exactly like an explicit category axis: it falls
710    /// back to the number of points when the data carry no names, and it
711    /// insets the band so that the first and the last point do not sit
712    /// on the edge of the grid.
713    ///
714    /// # Arguments
715    ///
716    /// - `&BBox` - Shared reference to a `BBox`.
717    /// - `&[SeriesOption]` - Shared reference to a `[SeriesOption]`.
718    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
719    ///
720    /// # Returns
721    ///
722    /// - `AxisLayout` - A `AxisLayout` value.
723    fn default_x_axis(
724        &self,
725        rect: &BBox,
726        series: &[SeriesOption],
727        measurer: &TextMeasurer,
728    ) -> AxisLayout {
729        let categories: Vec<String> = collect_categories(series);
730        let count: usize = if categories.is_empty() {
731            category_count(series)
732        } else {
733            categories.len()
734        }
735        .max(1);
736        let band: f64 = rect.width() / count as f64;
737        let inset: f64 = band * 0.5;
738        let scale: Scale = if categories.is_empty() {
739            Scale::category_count(count, rect.min_x + inset, rect.max_x - inset, band)
740        } else {
741            Scale::category(categories, rect.min_x + inset, rect.max_x - inset, band)
742        };
743        let ticks: Vec<AxisTick> = category_ticks(&scale);
744        // The labels of a dense axis are sampled: the interval tells the
745        // renderer how many ticks it may skip between two labels.
746        let default_axis: AxisOption = AxisOption::category();
747        let (label_space, _, label_interval) =
748            self.measure_label_space(&ticks, &default_axis, measurer, false, rect.width());
749        AxisLayout {
750            index: 0,
751            kind: AxisType::Category,
752            scale,
753            ticks,
754            side: AxisSide::Bottom,
755            offset: rect.max_y,
756            label_space,
757            name: None,
758            name_space: 0.0,
759            show: true,
760            labels: Vec::new(),
761            label_interval,
762        }
763    }
764
765    /// Builds the default y-axis of a chart without explicit axes.
766    ///
767    /// # Arguments
768    ///
769    /// - `&BBox` - Shared reference to a `BBox`.
770    /// - `&[SeriesOption]` - Shared reference to a `[SeriesOption]`.
771    ///
772    /// # Returns
773    ///
774    /// - `AxisLayout` - A `AxisLayout` value.
775    fn default_y_axis(&self, rect: &BBox, series: &[SeriesOption]) -> AxisLayout {
776        let extent: (f64, f64) = value_extent(series, None);
777        let (domain_min, domain_max) = nice_extent(extent.0, extent.1, DEFAULT_SPLIT_NUMBER);
778        let scale: Scale = Scale::linear(domain_min, domain_max, rect.max_y, rect.min_y);
779        let ticks: Vec<AxisTick> = place_ticks(
780            &scale,
781            value_ticks(domain_min, domain_max, DEFAULT_SPLIT_NUMBER),
782        );
783        AxisLayout {
784            index: 0,
785            kind: AxisType::Value,
786            scale,
787            ticks,
788            side: AxisSide::Left,
789            offset: rect.min_x,
790            label_space: 40.0,
791            name: None,
792            name_space: 0.0,
793            show: true,
794            labels: Vec::new(),
795            label_interval: 1,
796        }
797    }
798
799    /// Measures the space an axis' labels need.
800    ///
801    /// The method returns the width and the height of the widest label,
802    /// and the interval between two labels: an axis that has more than
803    /// categories than fit skips the labels in between.
804    ///
805    /// # Arguments
806    ///
807    /// - `&[AxisTick]` - Shared reference to a `[AxisTick]`.
808    /// - `&AxisOption` - Shared reference to a `AxisOption`.
809    /// - `&TextMeasurer` - Shared reference to a `TextMeasurer`.
810    /// - `bool` - A boolean (`bool`).
811    /// - `f64` - A 64-bit float (`f64`).
812    ///
813    /// # Returns
814    ///
815    /// - `(f64, f64, usize)` - A `(f64, f64, usize)` value.
816    fn measure_label_space(
817        &self,
818        ticks: &[AxisTick],
819        axis: &AxisOption,
820        measurer: &TextMeasurer,
821        vertical: bool,
822        extent: f64,
823    ) -> (f64, f64, usize) {
824        let font_size: f64 = axis
825            .axis_label
826            .as_ref()
827            .and_then(|label: &LabelOption| label.font_size)
828            .unwrap_or(DEFAULT_AXIS_LABEL_FONT_SIZE);
829        let font: String = font_string(font_size);
830        let mut widest: f64 = 0.0_f64;
831        let mut tallest: f64 = 0.0_f64;
832        for tick in ticks {
833            let metrics: TextMetrics = measurer.measure(&tick.label, &font);
834            widest = widest.max(metrics.width);
835            tallest = tallest.max(metrics.height);
836        }
837        let tick_length: f64 = if axis
838            .axis_tick
839            .as_ref()
840            .map(|tick: &AxisTickOption| tick.show)
841            .unwrap_or(false)
842        {
843            DEFAULT_AXIS_TICK_LENGTH
844        } else {
845            0.0
846        };
847        let space: f64 =
848            (if vertical { widest } else { tallest }) + tick_length + DEFAULT_AXIS_LABEL_GAP;
849
850        // The interval between two labels: the label has to fit in the
851        // space one tick leaves.
852        let label_extent: f64 = if vertical { tallest } else { widest };
853        let spacing: f64 = if ticks.is_empty() {
854            extent
855        } else {
856            extent / ticks.len() as f64
857        };
858        let interval = if label_extent > 0.0 && spacing > 0.0 {
859            ((label_extent / spacing).ceil() as usize).max(1)
860        } else {
861            1
862        };
863        (space, interval as f64, interval)
864    }
865
866    /// Builds the scale and the ticks of an axis.
867    ///
868    /// # Arguments
869    ///
870    /// - `&AxisOption` - Shared reference to a `AxisOption`.
871    /// - `ScaleKind` - A `ScaleKind` parameter.
872    /// - `&BBox` - Shared reference to a `BBox`.
873    /// - `&[SeriesOption]` - Shared reference to a `[SeriesOption]`.
874    /// - `bool` - A boolean (`bool`).
875    ///
876    /// # Returns
877    ///
878    /// - `(Scale, Vec<AxisTick>)` - A `(Scale, Vec<AxisTick>)` value.
879    fn build_scale(
880        &self,
881        axis: &AxisOption,
882        default_kind: ScaleKind,
883        rect: &BBox,
884        series: &[SeriesOption],
885        horizontal: bool,
886    ) -> (Scale, Vec<AxisTick>) {
887        let (range_min, range_max) = if horizontal {
888            (rect.min_x, rect.max_x)
889        } else {
890            (rect.max_y, rect.min_y)
891        };
892
893        let kind: ScaleKind = match axis.axis_type {
894            AxisType::Category => ScaleKind::Category,
895            AxisType::Value => ScaleKind::Linear,
896            AxisType::Time => ScaleKind::Time,
897            AxisType::Log => ScaleKind::Log,
898        };
899        let _: ScaleKind = default_kind;
900
901        match kind {
902            ScaleKind::Category => {
903                let categories: Vec<String> = if !axis.data.is_empty() {
904                    axis.data
905                        .iter()
906                        .map(|value: &DataValue| value.as_text().unwrap_or_default().to_string())
907                        .collect()
908                } else {
909                    collect_categories(series)
910                };
911                let count: usize = if categories.is_empty() {
912                    category_count(series)
913                } else {
914                    categories.len()
915                };
916                let band: f64 = (range_max - range_min).abs() / count.max(1) as f64;
917                let inset: f64 = band * 0.5;
918                let scale: Scale = if categories.is_empty() {
919                    Scale::category_count(count, range_min + inset, range_max - inset, band)
920                } else {
921                    Scale::category(categories, range_min + inset, range_max - inset, band)
922                };
923                let ticks: Vec<AxisTick> = category_ticks(&scale);
924                (scale, ticks)
925            }
926            ScaleKind::Log => {
927                let extent: (f64, f64) = value_extent(series, Some(axis));
928                let (domain_min, domain_max) =
929                    nice_extent(extent.0, extent.1, DEFAULT_SPLIT_NUMBER);
930                let base: f64 = axis.log_base.unwrap_or(10.0);
931                let scale: Scale = Scale::log(
932                    domain_min.max(f64::MIN_POSITIVE),
933                    domain_max.max(f64::MIN_POSITIVE),
934                    range_min,
935                    range_max,
936                    base,
937                );
938                let ticks: Vec<AxisTick> =
939                    log_ticks(domain_min, domain_max, base, range_min, range_max);
940                (scale, ticks)
941            }
942            _ => {
943                let extent: (f64, f64) = value_extent(series, Some(axis));
944                let mut domain_min: f64 = axis.min.unwrap_or(extent.0);
945                let mut domain_max: f64 = axis.max.unwrap_or(extent.1);
946                if domain_min == domain_max {
947                    domain_min -= 1.0;
948                    domain_max += 1.0;
949                }
950                // The interval is decided once, here: the ticks are
951                // spaced by the very interval the bounds were snapped
952                // to, so that the last tick lands on the last bound.
953                let (nice_min, nice_max, interval) =
954                    nice_scale(domain_min, domain_max, DEFAULT_SPLIT_NUMBER);
955                if axis.min.is_none() {
956                    domain_min = nice_min;
957                }
958                if axis.max.is_none() {
959                    domain_max = nice_max;
960                }
961                let scale: Scale = Scale::linear(domain_min, domain_max, range_min, range_max);
962                let ticks: Vec<AxisTick> =
963                    place_ticks(&scale, ticks_at_interval(domain_min, domain_max, interval));
964                (scale, ticks)
965            }
966        }
967    }
968
969    /// Computes the layout of every series.
970    ///
971    /// # Arguments
972    ///
973    /// - `&ChartOption` - Shared reference to a `ChartOption`.
974    /// - `&[GridLayout]` - Shared reference to a `[GridLayout]`.
975    ///
976    /// # Returns
977    ///
978    /// - `Vec<SeriesLayout>` - A `Vec<SeriesLayout>` value.
979    fn measure_series(&self, option: &ChartOption, grids: &[GridLayout]) -> Vec<SeriesLayout> {
980        let mut layouts: Vec<SeriesLayout> = Vec::with_capacity(option.series.len());
981        for (index, series) in option.series.iter().enumerate() {
982            if !series.show {
983                continue;
984            }
985            let grid_index: usize = series
986                .x_axis_index
987                .or(series.y_axis_index)
988                .map(|_| 0)
989                .unwrap_or(0);
990            let grid: Option<&GridLayout> = grids.get(grid_index);
991            let (points, stack_index, stack_size, band_width) = match series.kind {
992                // The non-cartesian series place themselves: all they
993                // need from the layout is their value.
994                SeriesType::Pie
995                | SeriesType::Funnel
996                | SeriesType::Radar
997                | SeriesType::Gauge
998                | SeriesType::Map => (self.place_values(series), 0, 1, 0.0),
999                _ => match grid {
1000                    Some(grid) => {
1001                        let x_axis: Option<&AxisLayout> =
1002                            grid.x_axis(series.x_axis_index.unwrap_or(0));
1003                        let y_axis: Option<&AxisLayout> =
1004                            grid.y_axis(series.y_axis_index.unwrap_or(0));
1005                        match (x_axis, y_axis) {
1006                            (Some(x_axis), Some(y_axis)) => {
1007                                let points: Vec<SeriesPoint> =
1008                                    self.place_points(series, x_axis, y_axis);
1009                                let (stack_index, stack_size) = stack_position(option, index);
1010                                (points, stack_index, stack_size, x_axis.scale.band_width())
1011                            }
1012                            _ => (Vec::new(), 0, 1, 0.0),
1013                        }
1014                    }
1015                    None => (Vec::new(), 0, 1, 0.0),
1016                },
1017            };
1018            layouts.push(SeriesLayout {
1019                index,
1020                kind: series.kind,
1021                name: series.name.clone(),
1022                grid_index,
1023                points,
1024                stack: series.stack.clone(),
1025                stack_index,
1026                stack_size,
1027                plot_rect: self.get_plot_rect(),
1028                band_width,
1029            });
1030        }
1031        layouts
1032    }
1033
1034    /// Places the values of a non-cartesian series.
1035    ///
1036    /// A pie, a funnel, a radar, and a gauge all decide for themselves
1037    /// where their values go; the layout only carries the values.    ///
1038    /// # Arguments
1039    ///
1040    /// - `&SeriesOption` - Shared reference to a `SeriesOption`.
1041    ///
1042    /// # Returns
1043    ///
1044    /// - `Vec<SeriesPoint>` - A `Vec<SeriesPoint>` value.
1045    fn place_values(&self, series: &SeriesOption) -> Vec<SeriesPoint> {
1046        series
1047            .data
1048            .iter()
1049            .enumerate()
1050            .map(|(data_index, value): (usize, &DataValue)| {
1051                let (name, number) = match value {
1052                    DataValue::List(items) => (
1053                        items
1054                            .first()
1055                            .and_then(|item: &DataValue| item.as_text())
1056                            .unwrap_or_default()
1057                            .to_string(),
1058                        items
1059                            .get(1)
1060                            .and_then(|item: &DataValue| item.to_number())
1061                            .unwrap_or(0.0),
1062                    ),
1063                    DataValue::Object(_) => (
1064                        value
1065                            .get(DATA_FIELD_NAME)
1066                            .and_then(|item: &DataValue| item.as_text())
1067                            .unwrap_or_default()
1068                            .to_string(),
1069                        value
1070                            .get(DATA_FIELD_VALUE)
1071                            .and_then(|item: &DataValue| item.to_number())
1072                            .unwrap_or(0.0),
1073                    ),
1074                    other => (
1075                        other.as_text().unwrap_or_default().to_string(),
1076                        other.to_number().unwrap_or(0.0),
1077                    ),
1078                };
1079                SeriesPoint {
1080                    x: 0.0,
1081                    y: 0.0,
1082                    value: number,
1083                    name,
1084                    data_index,
1085                }
1086            })
1087            .collect()
1088    }
1089
1090    /// Places the data points of a cartesian series in pixel space.
1091    ///
1092    /// # Arguments
1093    ///
1094    /// - `&SeriesOption` - Shared reference to a `SeriesOption`.
1095    /// - `&AxisLayout` - Shared reference to a `AxisLayout`.
1096    ///
1097    /// # Returns
1098    ///
1099    /// - `Vec<SeriesPoint>` - A `Vec<SeriesPoint>` value.
1100    fn place_points(
1101        &self,
1102        series: &SeriesOption,
1103        x_axis: &AxisLayout,
1104        y_axis: &AxisLayout,
1105    ) -> Vec<SeriesPoint> {
1106        let mut points: Vec<SeriesPoint> = Vec::with_capacity(series.data.len());
1107        let category_axis: bool = x_axis.scale.is_ordinal();
1108        for (data_index, value) in series.data.iter().enumerate() {
1109            let (name, x_value, y_value) = match value {
1110                DataValue::List(items) => {
1111                    let first: Option<f64> =
1112                        items.first().and_then(|item: &DataValue| item.to_number());
1113                    let second: Option<f64> =
1114                        items.get(1).and_then(|item: &DataValue| item.to_number());
1115                    let label: String = items
1116                        .first()
1117                        .and_then(|item: &DataValue| item.as_text())
1118                        .map(|text: &str| text.to_string())
1119                        .unwrap_or_default();
1120                    // The list holds the category and the value, the two
1121                    // values, or only the value. A list whose first
1122                    // element is a name has no number in front of the
1123                    // value, and the point then belongs to the category
1124                    // of its own position in the data.
1125                    match (first, second) {
1126                        (Some(x), Some(y)) => (label, x, y),
1127                        (None, Some(y)) => (label, data_index as f64, y),
1128                        (Some(y), None) => (label, data_index as f64, y),
1129                        (None, None) => continue,
1130                    }
1131                }
1132                DataValue::Object(_) => {
1133                    let name: String = value
1134                        .get(DATA_FIELD_NAME)
1135                        .and_then(|item: &DataValue| item.as_text())
1136                        .unwrap_or_default()
1137                        .to_string();
1138                    // The value of a custom series is a pair: the
1139                    // point it is drawn at. Every other series has a
1140                    // single value, and takes its position from the
1141                    // order of the data.
1142                    let (x_value, y_value) = match value.get(DATA_FIELD_VALUE) {
1143                        Some(DataValue::List(items)) => (
1144                            items.first().and_then(|item: &DataValue| item.to_number()),
1145                            items.get(1).and_then(|item: &DataValue| item.to_number()),
1146                        ),
1147                        other => (None, other.and_then(|item: &DataValue| item.to_number())),
1148                    };
1149                    let Some(y_value) = y_value else {
1150                        continue;
1151                    };
1152                    let x_value: f64 = match x_value {
1153                        Some(x_value) => x_value,
1154                        None => data_index as f64,
1155                    };
1156                    (name, x_value, y_value)
1157                }
1158                other => {
1159                    let y: f64 = match other.to_number() {
1160                        Some(y) => y,
1161                        None => continue,
1162                    };
1163                    (String::new(), data_index as f64, y)
1164                }
1165            };
1166            let x: f64 = if category_axis {
1167                x_axis.scale.map(data_index as f64)
1168            } else {
1169                x_axis.scale.map(x_value)
1170            };
1171            let y: f64 = y_axis.scale.map(y_value);
1172            points.push(SeriesPoint {
1173                x,
1174                y,
1175                value: y_value,
1176                name,
1177                data_index,
1178            });
1179        }
1180        points
1181    }
1182    /// Returns the map with the given name.
1183    ///
1184    /// A chart can hold more than one map: a geo component and a map
1185    /// series may draw different maps, and two map series may draw the
1186    /// same one twice.
1187    ///
1188    /// # Arguments
1189    ///
1190    /// - `&str` - Shared reference to a `str`.
1191    ///
1192    /// # Returns
1193    ///
1194    /// - `Option<&GeoLayout>` - `Some(...)` on success, `None` otherwise.
1195    pub fn geo(&self, name: &str) -> Option<&GeoLayout> {
1196        self.get_geo()
1197            .iter()
1198            .find(|layout: &&GeoLayout| layout.name == name)
1199    }
1200}
1201
1202/// Inherent implementation of [`VisualMap`].
1203impl VisualMap {
1204    /// Resolves a visual map option against the values it applies to.
1205    ///
1206    /// The range of the map is the range of the values, unless the
1207    /// option names one. The colours are the ones the option names, or
1208    /// the ones ECharts falls back to.
1209    ///
1210    /// # Arguments
1211    ///
1212    /// - `&VisualMapOption` - Shared reference to a `VisualMapOption`.
1213    /// - `&[f64]` - Shared reference to a `[f64]`.
1214    pub fn resolve(option: &VisualMapOption, values: &[f64]) -> Self {
1215        let mut min: f64 = option.min.unwrap_or(f64::INFINITY);
1216        let mut max: f64 = option.max.unwrap_or(f64::NEG_INFINITY);
1217        for value in values {
1218            min = min.min(*value);
1219            max = max.max(*value);
1220        }
1221        if !min.is_finite() {
1222            min = 0.0;
1223        }
1224        if !max.is_finite() {
1225            max = 0.0;
1226        }
1227        if max <= min {
1228            // A map of a single value still has a range, or the value
1229            // would sit at the very edge of the palette.
1230            max = min + 1.0;
1231        }
1232        let colors: Vec<Color> = parse_colors(&option.colors);
1233        Self {
1234            visual_type: option.visual_type,
1235            min,
1236            max,
1237            colors,
1238            split_number: option
1239                .split_number
1240                .unwrap_or(DEFAULT_VISUAL_MAP_SPLIT)
1241                .max(1),
1242            pieces: parse_pieces(&option.pieces),
1243            out_of_range: option.out_of_range.as_deref().and_then(Color::parse),
1244        }
1245    }
1246
1247    /// Returns the colour of a value.
1248    ///
1249    /// A continuous map mixes the colours of its palette, and a
1250    /// piecewise one picks the band the value falls into. The bands the
1251    /// option names itself win over both.
1252    ///
1253    /// # Arguments
1254    ///
1255    /// - `f64` - A 64-bit float (`f64`).
1256    ///
1257    /// # Returns
1258    ///
1259    /// - `Color` - A `Color` value.
1260    pub fn color_of(&self, value: f64) -> Color {
1261        if !self.get_pieces().is_empty() {
1262            for piece in self.get_pieces() {
1263                if value >= piece.min && value < piece.max {
1264                    return piece.color;
1265                }
1266            }
1267            if let Some(color) = self.get_out_of_range() {
1268                return color;
1269            }
1270        }
1271        if self.get_colors().is_empty() {
1272            return Color::rgb(191, 68, 76);
1273        }
1274        let span: f64 = self.get_max() - self.get_min();
1275        let position: f64 = if span.abs() < f64::EPSILON {
1276            0.0
1277        } else {
1278            ((value - self.get_min()) / span).clamp(0.0, 1.0)
1279        };
1280        match self.get_visual_type() {
1281            VisualMapType::Piecewise => {
1282                let bands: usize = self.get_split_number().max(1);
1283                let index: usize = ((position * bands as f64).floor() as usize).min(bands - 1);
1284                self.get_colors()[index % self.get_colors().len()]
1285            }
1286            VisualMapType::Continuous => {
1287                let place: f64 = position * (self.get_colors().len() - 1) as f64;
1288                let low: usize = place.floor() as usize;
1289                let high: usize = place.ceil() as usize;
1290                if low == high {
1291                    return self.get_colors()[low];
1292                }
1293                self.get_colors()[low].lerp(self.get_colors()[high], place - low as f64)
1294            }
1295        }
1296    }
1297}