Skip to main content

chart/
plan.rs

1//! A chart laid out at a size: every mark, axis and label in pixels from the
2//! chart's top-left corner, with no window involved.
3//!
4//! Bars sharing a band stack, positive values up and negative down. Lines,
5//! areas and points take their series in ascending x, and a missing y breaks
6//! a line or area. A channel's [`Scale`] and [`Sort`] shape its domain, and its
7//! title sits under the bottom axis or over the left one.
8
9use std::{
10    collections::{HashMap, HashSet},
11    f32::consts::TAU,
12    sync::Arc,
13};
14
15use gpui::SharedString;
16
17use crate::{
18    data::Column,
19    decimate,
20    model::{Channel, Chart, Encoding, Kind, Layer, Mark, Scale, Sort},
21    scale::{self, Band, Linear, Ticks},
22    time,
23};
24
25/// Around the whole chart.
26const PAD: f32 = 8.0;
27/// Between a label and what it labels.
28const GAP: f32 = 6.0;
29const SWATCH: f32 = 10.0;
30/// Between legend entries.
31const LEGEND_GAP: f32 = 16.0;
32/// Roughly how far apart ticks stand along x, and along y.
33const X_SPACING: f32 = 80.0;
34const Y_SPACING: f32 = 40.0;
35const BAND_PADDING: f32 = 0.2;
36/// Below this either way there is no plot to draw in.
37const MIN_PLOT: f32 = 16.0;
38/// How far from the hovered x another series' point may be and still be read
39/// out with it.
40const SAME_X: f32 = 1.0;
41
42/// A missing code in a discrete column.
43const MISSING: u32 = u32::MAX;
44
45#[derive(Clone, Copy, Debug, Default, PartialEq)]
46pub struct Rect {
47    pub x: f32,
48    pub y: f32,
49    pub w: f32,
50    pub h: f32,
51}
52
53impl Rect {
54    pub fn right(&self) -> f32 {
55        self.x + self.w
56    }
57
58    pub fn bottom(&self) -> f32 {
59        self.y + self.h
60    }
61
62    pub fn contains(&self, [x, y]: [f32; 2]) -> bool {
63        x >= self.x && x <= self.right() && y >= self.y && y <= self.bottom()
64    }
65}
66
67/// A line of text one line high, its top-left at `at`, `width` as measured.
68#[derive(Clone, Debug, PartialEq)]
69pub struct Label {
70    pub text: SharedString,
71    pub at: [f32; 2],
72    pub width: f32,
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum Side {
77    Bottom,
78    Left,
79}
80
81/// `at` is x on a bottom axis and y on a left one.
82#[derive(Clone, Debug, PartialEq)]
83pub struct Tick {
84    pub at: f32,
85    pub label: Option<Label>,
86}
87
88/// `grid` for an axis of values, whose ticks run lines across the plot.
89#[derive(Clone, Debug, PartialEq)]
90pub struct Axis {
91    pub side: Side,
92    pub ticks: Vec<Tick>,
93    pub grid: bool,
94    pub title: Option<Label>,
95}
96
97#[derive(Clone, Debug, PartialEq)]
98pub struct Swatch {
99    pub series: usize,
100    pub swatch: Rect,
101    pub label: Label,
102}
103
104/// Angles in radians, clockwise from twelve o'clock.
105#[derive(Clone, Copy, Debug, PartialEq)]
106pub struct Slice {
107    pub center: [f32; 2],
108    pub radius: f32,
109    pub start: f32,
110    pub end: f32,
111}
112
113#[derive(Clone, Debug, PartialEq)]
114pub enum Marks {
115    Bars(Vec<Rect>),
116    /// Each a run of points with no gap in it.
117    Lines(Vec<Vec<[f32; 2]>>),
118    /// Each a closed outline: a run along the top, then back along zero.
119    Areas(Vec<Vec<[f32; 2]>>),
120    /// At most one to a pixel.
121    Points(Vec<[f32; 2]>),
122    Slices(Vec<Slice>),
123}
124
125/// One colour's marks in one layer. `name` is its value of the colour field.
126#[derive(Clone, Debug, PartialEq)]
127pub struct Series {
128    pub name: Option<SharedString>,
129    /// Its place in the chart's colours.
130    pub color: usize,
131    pub mark: Mark,
132    pub marks: Marks,
133}
134
135/// What is under the pointer.
136#[derive(Clone, Debug, PartialEq)]
137pub struct Tip {
138    pub title: SharedString,
139    /// Colour and the value read out in it.
140    pub rows: Vec<(usize, SharedString)>,
141    /// Colour and the point read out.
142    pub points: Vec<(usize, [f32; 2])>,
143    /// The band read out.
144    pub band: Option<Rect>,
145}
146
147#[derive(Clone, Debug)]
148pub struct Plan {
149    pub size: [f32; 2],
150    /// What the marks are drawn in. For arcs, the square the pie fills.
151    pub plot: Rect,
152    pub title: Option<Label>,
153    /// The colour channel's title, ahead of the swatches.
154    pub legend_title: Option<Label>,
155    pub legend: Vec<Swatch>,
156    pub axes: Vec<Axis>,
157    /// Paint order.
158    pub series: Vec<Series>,
159    /// Each colour's value of the colour field. `None` for a layer's own
160    /// colour, where it has no colour field.
161    pub colors: Vec<Option<SharedString>>,
162    chart: Chart,
163    hover: Hover,
164}
165
166#[derive(Clone, Debug, Default)]
167struct Hover {
168    bands: Option<Bands>,
169    traces: Vec<Trace>,
170    slices: Option<Slices>,
171}
172
173/// Bars' values by band.
174#[derive(Clone, Debug)]
175struct Bands {
176    band: Band,
177    vertical: bool,
178    across: (f32, f32),
179    labels: Vec<SharedString>,
180    /// Colour, and its value in each band, `NaN` for none.
181    values: Vec<(usize, Vec<f64>)>,
182}
183
184/// One colour of a line, area or point layer, ascending in x: each point and
185/// its row.
186#[derive(Clone, Debug)]
187struct Trace {
188    layer: usize,
189    color: usize,
190    points: Vec<([f32; 2], u32)>,
191}
192
193#[derive(Clone, Debug)]
194struct Slices {
195    slices: Vec<(usize, Slice, f64)>,
196    total: f64,
197}
198
199/// `chart` laid out at `size`, its labels `line` high and as wide as `measure`
200/// says. `None` for what cannot be drawn: a field the data does not hold, a
201/// quantitative field over text, a colour that is not discrete, layers that
202/// disagree on which axis is discrete, a layered arc, or a size too small to
203/// hold a plot.
204pub fn plan(
205    chart: &Chart,
206    size: [f32; 2],
207    line: f32,
208    measure: &mut dyn FnMut(&str) -> f32,
209) -> Option<Plan> {
210    let colors = Colors::of(chart)?;
211    let mut layout = Layout {
212        size,
213        line,
214        top: PAD,
215        reserved: 0.0,
216        measure,
217    };
218    let first_title = |channel: fn(&Encoding) -> &Option<Channel>| {
219        chart
220            .layers
221            .iter()
222            .find_map(|layer| channel(&layer.encoding).as_ref()?.title.clone())
223    };
224    let title = chart.title.clone().map(|text| layout.line_at(text, PAD));
225    let (legend_title, legend) = layout.legend(first_title(|e| &e.color), &colors.names);
226    let arc = chart.layers.iter().any(|layer| layer.mark == Mark::Arc);
227    let x_title = first_title(|e| &e.x).filter(|_| !arc);
228    if x_title.is_some() {
229        layout.reserved = line + GAP;
230    }
231    let y_title = first_title(|e| &e.y)
232        .filter(|_| !arc)
233        .map(|text| layout.line_at(text, PAD));
234
235    let (plot, mut axes, series, hover) = match (arc, chart.layers.as_slice()) {
236        (true, [layer]) => arcs(layer, &colors, &layout)?,
237        (true, _) | (false, []) => return None,
238        (false, _) => cartesian(chart, &colors, &mut layout)?,
239    };
240    let x_title = x_title.map(|text| {
241        let label = layout.label(text, [0.0, 0.0]);
242        Label {
243            at: [
244                plot.x + (plot.w - label.width) / 2.0,
245                plot.bottom() + GAP + line + GAP,
246            ],
247            ..label
248        }
249    });
250    for axis in &mut axes {
251        axis.title = match axis.side {
252            Side::Bottom => x_title.clone(),
253            Side::Left => y_title.clone(),
254        };
255    }
256    Some(Plan {
257        size,
258        plot,
259        title,
260        legend_title,
261        legend,
262        axes,
263        series,
264        colors: colors.names,
265        chart: chart.clone(),
266        hover,
267    })
268}
269
270impl Plan {
271    pub fn chart(&self) -> &Chart {
272        &self.chart
273    }
274
275    /// What `point` is over, if anything.
276    pub fn hit(&self, point: [f32; 2]) -> Option<Tip> {
277        if let Some(Slices { slices, total }) = &self.hover.slices {
278            let (center, radius) = slices.first().map(|(_, s, _)| (s.center, s.radius))?;
279            let (dx, dy) = (point[0] - center[0], point[1] - center[1]);
280            if dx.hypot(dy) > radius {
281                return None;
282            }
283            let angle = dx.atan2(-dy).rem_euclid(TAU);
284            let (color, _, value) = slices
285                .iter()
286                .find(|(_, slice, _)| angle >= slice.start && angle < slice.end)?;
287            let share = value / total * 100.0;
288            return Some(Tip {
289                title: self.colors[*color].clone().unwrap_or_default(),
290                rows: vec![(
291                    *color,
292                    format!("{} ({share:.1}%)", scale::value(*value)).into(),
293                )],
294                points: Vec::new(),
295                band: None,
296            });
297        }
298        if !self.plot.contains(point) {
299            return None;
300        }
301
302        let mut tip = Tip {
303            title: SharedString::default(),
304            rows: Vec::new(),
305            points: Vec::new(),
306            band: None,
307        };
308        if let Some(bands) = &self.hover.bands {
309            let Bands {
310                band,
311                vertical,
312                across,
313                labels,
314                values,
315            } = bands;
316            let index = band.index(if *vertical { point[0] } else { point[1] })?;
317            let start = band.range.0 + band.step() * index as f32;
318            tip.title = labels[index].clone();
319            tip.band = Some(match vertical {
320                true => Rect {
321                    x: start,
322                    y: across.0,
323                    w: band.step(),
324                    h: across.1 - across.0,
325                },
326                false => Rect {
327                    x: across.0,
328                    y: start,
329                    w: across.1 - across.0,
330                    h: band.step(),
331                },
332            });
333            tip.rows.extend(values.iter().filter_map(|(color, values)| {
334                let value = values[index];
335                (!value.is_nan()).then(|| (*color, scale::value(value).into()))
336            }));
337        }
338
339        let gap = |p: &([f32; 2], u32)| (p.0[0] - point[0]).abs();
340        let at = self
341            .hover
342            .traces
343            .iter()
344            .filter_map(|trace| nearest(&trace.points, point[0]))
345            .min_by(|a, b| gap(a).total_cmp(&gap(b)))
346            .map(|p| p.0[0]);
347        if let Some(at) = at {
348            for trace in &self.hover.traces {
349                let Some(&(p, row)) = nearest(&trace.points, at) else {
350                    continue;
351                };
352                if (p[0] - at).abs() > SAME_X {
353                    continue;
354                }
355                let encoding = &self.chart.layers[trace.layer].encoding;
356                if tip.title.is_empty() {
357                    tip.title = self.text(trace.layer, encoding.x.as_ref()?, row as usize)?;
358                }
359                tip.rows.push((
360                    trace.color,
361                    self.text(trace.layer, encoding.y.as_ref()?, row as usize)?,
362                ));
363                tip.points.push((trace.color, p));
364            }
365        }
366        (!tip.rows.is_empty()).then_some(tip)
367    }
368
369    /// Row `row` of `channel`'s field in layer `layer`, read as its kind.
370    fn text(&self, layer: usize, channel: &Channel, row: usize) -> Option<SharedString> {
371        Some(
372            match self.chart.layers[layer].data.column(&channel.field)? {
373                Column::Text(text) => text.get(row).clone(),
374                Column::Number(values) => match channel.kind {
375                    Kind::Temporal => time::describe(values[row]).into(),
376                    _ => scale::value(values[row]).into(),
377                },
378            },
379        )
380    }
381}
382
383/// The point of `points`, ascending in x, nearest `at` along x.
384fn nearest(points: &[([f32; 2], u32)], at: f32) -> Option<&([f32; 2], u32)> {
385    let index = points.partition_point(|(p, _)| p[0] < at);
386    [index.checked_sub(1), Some(index)]
387        .into_iter()
388        .flatten()
389        .filter_map(|index| points.get(index))
390        .min_by(|a, b| (a.0[0] - at).abs().total_cmp(&(b.0[0] - at).abs()))
391}
392
393/// The chart's colours: every colour field's values across the layers, in
394/// the order met, then one for each layer with no colour field.
395struct Colors {
396    names: Vec<Option<SharedString>>,
397    layers: Vec<LayerColor>,
398}
399
400enum LayerColor {
401    /// Each row's colour.
402    Field(Vec<u32>),
403    Fixed(usize),
404}
405
406impl Colors {
407    fn of(chart: &Chart) -> Option<Self> {
408        let mut names = Vec::new();
409        let mut index: HashMap<SharedString, u32> = HashMap::new();
410        let mut layers = Vec::with_capacity(chart.layers.len());
411        for layer in &chart.layers {
412            let Some(color) = &layer.encoding.color else {
413                layers.push(None);
414                continue;
415            };
416            if !discrete(color.kind) {
417                return None;
418            }
419            let domain = Discrete::of(layer.data.column(&color.field)?, &color.sort);
420            let global: Vec<u32> = domain
421                .labels
422                .into_iter()
423                .map(|label| {
424                    *index.entry(label.clone()).or_insert_with(|| {
425                        names.push(Some(label));
426                        names.len() as u32 - 1
427                    })
428                })
429                .collect();
430            let codes = domain.codes.iter().map(|&code| match code {
431                MISSING => MISSING,
432                code => global[code as usize],
433            });
434            layers.push(Some(LayerColor::Field(codes.collect())));
435        }
436        let layers = layers
437            .into_iter()
438            .map(|color| {
439                color.unwrap_or_else(|| {
440                    names.push(None);
441                    LayerColor::Fixed(names.len() - 1)
442                })
443            })
444            .collect();
445        Some(Self { names, layers })
446    }
447
448    fn of_row(&self, layer: usize, row: usize) -> Option<usize> {
449        match &self.layers[layer] {
450            LayerColor::Fixed(color) => Some(*color),
451            LayerColor::Field(codes) => (codes[row] != MISSING).then_some(codes[row] as usize),
452        }
453    }
454
455    fn len(&self) -> usize {
456        self.names.len()
457    }
458}
459
460/// A column's distinct values in order, and each row's index into them.
461struct Discrete {
462    labels: Vec<SharedString>,
463    codes: Arc<[u32]>,
464}
465
466impl Discrete {
467    fn of(column: &Column, sort: &Sort) -> Self {
468        let (labels, codes, numbers): (Vec<SharedString>, Arc<[u32]>, Option<Vec<f64>>) =
469            match column {
470                Column::Text(text) => (text.names.to_vec(), text.codes.clone(), None),
471                Column::Number(values) => {
472                    let mut index = HashMap::new();
473                    let mut distinct = Vec::new();
474                    let codes = values
475                        .iter()
476                        .map(|&v| match v.is_nan() {
477                            true => MISSING,
478                            false => *index.entry(v.to_bits()).or_insert_with(|| {
479                                distinct.push(v);
480                                distinct.len() as u32 - 1
481                            }),
482                        })
483                        .collect();
484                    let labels = distinct.iter().map(|&v| scale::value(v).into()).collect();
485                    (labels, codes, Some(distinct))
486                }
487            };
488        let mut order: Vec<usize> = (0..labels.len()).collect();
489        match sort {
490            Sort::Data => return Self { labels, codes },
491            Sort::Ascending | Sort::Descending => {
492                order.sort_by(|&a, &b| match &numbers {
493                    Some(values) => values[a].total_cmp(&values[b]),
494                    None => labels[a].cmp(&labels[b]),
495                });
496                if *sort == Sort::Descending {
497                    order.reverse();
498                }
499            }
500            Sort::Explicit(listed) => {
501                let rank = |index: &usize| {
502                    listed
503                        .iter()
504                        .position(|name| *name == labels[*index])
505                        .unwrap_or(listed.len())
506                };
507                order.sort_by_key(rank);
508            }
509        }
510        let mut rank = vec![0u32; labels.len()];
511        for (new, &old) in order.iter().enumerate() {
512            rank[old] = new as u32;
513        }
514        Self {
515            labels: order.iter().map(|&old| labels[old].clone()).collect(),
516            codes: codes
517                .iter()
518                .map(|&code| match code {
519                    MISSING => MISSING,
520                    code => rank[code as usize],
521                })
522                .collect(),
523        }
524    }
525}
526
527fn discrete(kind: Kind) -> bool {
528    matches!(kind, Kind::Ordinal | Kind::Nominal)
529}
530
531/// Ticks for a quantitative or temporal channel whose data spans `lo..=hi`.
532fn continuous(channel: &Channel, lo: f64, hi: f64, count: usize) -> Ticks {
533    let Scale { zero, domain, nice } = channel.scale;
534    match (channel.kind, domain) {
535        (Kind::Temporal, Some([lo, hi])) => scale::temporal(lo, hi, count),
536        (Kind::Temporal, None) => scale::temporal(lo, hi, count),
537        (_, Some([lo, hi])) => scale::linear(lo, hi, count, false),
538        (_, None) if zero => scale::linear(lo.min(0.0), hi.max(0.0), count, nice),
539        (_, None) => scale::linear(lo, hi, count, nice),
540    }
541}
542
543/// The chart's size, and how far down its title and legend reach.
544struct Layout<'a> {
545    size: [f32; 2],
546    line: f32,
547    top: f32,
548    /// Kept under the bottom axis, for its title.
549    reserved: f32,
550    measure: &'a mut dyn FnMut(&str) -> f32,
551}
552
553impl Layout<'_> {
554    fn label(&mut self, text: SharedString, at: [f32; 2]) -> Label {
555        let width = (self.measure)(&text);
556        Label { text, at, width }
557    }
558
559    /// A full-width line of the header, at `x`.
560    fn line_at(&mut self, text: SharedString, x: f32) -> Label {
561        let label = self.label(text, [x, self.top]);
562        self.top += self.line + GAP;
563        label
564    }
565
566    /// `title`, then one entry per named series, flowing left to right and
567    /// wrapping.
568    fn legend(
569        &mut self,
570        title: Option<SharedString>,
571        names: &[Option<SharedString>],
572    ) -> (Option<Label>, Vec<Swatch>) {
573        let mut swatches = Vec::new();
574        let mut x = PAD;
575        let title = title
576            .filter(|_| names.iter().any(Option::is_some))
577            .map(|text| {
578                let label = self.label(text, [PAD, self.top]);
579                x += label.width + GAP;
580                label
581            });
582        for (series, name) in names.iter().enumerate() {
583            let Some(name) = name else { continue };
584            let label = self.label(name.clone(), [0.0, 0.0]);
585            let width = SWATCH + GAP + label.width;
586            if x > PAD && x + width > self.size[0] - PAD {
587                x = PAD;
588                self.top += self.line;
589            }
590            swatches.push(Swatch {
591                series,
592                swatch: Rect {
593                    x,
594                    y: self.top + (self.line - SWATCH) / 2.0,
595                    w: SWATCH,
596                    h: SWATCH,
597                },
598                label: Label {
599                    at: [x + SWATCH + GAP, self.top],
600                    ..label
601                },
602            });
603            x += width + LEGEND_GAP;
604        }
605        if !swatches.is_empty() {
606            self.top += self.line + GAP;
607        }
608        (title, swatches)
609    }
610
611    /// The plot's height once a bottom axis is taken out.
612    fn plot_height(&self) -> f32 {
613        self.size[1] - self.top - PAD - GAP - self.line - self.reserved
614    }
615
616    /// The plot beside left labels `left` wide, over a bottom axis.
617    fn plot(&self, left: f32) -> Option<Rect> {
618        let x = PAD + left + GAP;
619        let plot = Rect {
620            x,
621            y: self.top,
622            w: self.size[0] - x - PAD,
623            h: self.plot_height(),
624        };
625        (plot.w >= MIN_PLOT && plot.h >= MIN_PLOT).then_some(plot)
626    }
627
628    fn measured(&mut self, labels: &[SharedString]) -> Vec<Label> {
629        labels
630            .iter()
631            .map(|text| self.label(text.clone(), [0.0, 0.0]))
632            .collect()
633    }
634
635    /// A left axis of measured `labels`, each centred on its `at` and
636    /// right-aligned against `plot`. Past one to a line, every k-th.
637    fn left(&self, labels: Vec<Label>, at: &[f32], plot: Rect, grid: bool) -> Axis {
638        let every = spacing(at).map_or(1, |step| (self.line / step).ceil().max(1.0) as usize);
639        Axis {
640            side: Side::Left,
641            ticks: labels
642                .into_iter()
643                .zip(at)
644                .enumerate()
645                .map(|(index, (label, &y))| Tick {
646                    at: y,
647                    label: (index % every == 0).then(|| Label {
648                        at: [plot.x - GAP - label.width, y - self.line / 2.0],
649                        ..label
650                    }),
651                })
652                .collect(),
653            grid,
654            title: None,
655        }
656    }
657
658    /// A bottom axis under `plot`, each label centred on its `at` and kept
659    /// inside the chart. Past what fits side by side, every k-th.
660    fn bottom(&mut self, labels: &[SharedString], at: &[f32], plot: Rect, grid: bool) -> Axis {
661        let measured = self.measured(labels);
662        let every = spacing(at).map_or(1, |step| {
663            ((widest(&measured) + GAP) / step).ceil().max(1.0) as usize
664        });
665        let ticks = measured
666            .into_iter()
667            .zip(at)
668            .enumerate()
669            .map(|(index, (label, &x))| Tick {
670                at: x,
671                label: (index % every == 0).then(|| Label {
672                    at: [
673                        (x - label.width / 2.0).clamp(0.0, (self.size[0] - label.width).max(0.0)),
674                        plot.bottom() + GAP,
675                    ],
676                    ..label
677                }),
678            })
679            .collect();
680        Axis {
681            side: Side::Bottom,
682            ticks,
683            grid,
684            title: None,
685        }
686    }
687}
688
689/// How far apart evenly spaced positions are.
690fn spacing(at: &[f32]) -> Option<f32> {
691    match at {
692        [a, b, ..] => Some((b - a).abs()),
693        _ => None,
694    }
695}
696
697fn widest(labels: &[Label]) -> f32 {
698    labels.iter().map(|l| l.width).fold(0.0, f32::max)
699}
700
701type Body = (Rect, Vec<Axis>, Vec<Series>, Hover);
702
703/// Where a row lands along the shared axis, in pixels.
704type Place = Box<dyn Fn(&Along, usize) -> f32>;
705
706/// Where a layer's rows fall along the axis the layers share their domain on.
707enum Along {
708    /// Codes into the chart's discrete domain.
709    Discrete(Vec<u32>),
710    Continuous(Arc<[f64]>),
711}
712
713impl Along {
714    fn valid(&self, row: usize) -> bool {
715        match self {
716            Along::Discrete(codes) => codes[row] != MISSING,
717            Along::Continuous(values) => !values[row].is_nan(),
718        }
719    }
720}
721
722/// A layer read for laying out: its data along the shared axis and along its
723/// values, and for bars, each band's total per colour.
724struct Read<'a> {
725    index: usize,
726    mark: Mark,
727    along: Along,
728    values: &'a Arc<[f64]>,
729    /// `sums[band * colours + colour]`, `NaN` for none.
730    sums: Vec<f64>,
731}
732
733fn cartesian(chart: &Chart, colors: &Colors, layout: &mut Layout) -> Option<Body> {
734    // Bars along y are the only marks that put the shared axis on the left.
735    let horizontal = chart.layers.iter().all(|layer| {
736        let encoding = &layer.encoding;
737        layer.mark == Mark::Bar
738            && encoding.y.as_ref().is_some_and(|y| discrete(y.kind))
739            && encoding.x.as_ref().is_some_and(|x| !discrete(x.kind))
740    });
741    let channels = |layer: &'_ Layer| -> Option<(Channel, Channel)> {
742        let (x, y) = (layer.encoding.x.clone()?, layer.encoding.y.clone()?);
743        Some(if horizontal { (y, x) } else { (x, y) })
744    };
745    let (shared, value) = channels(&chart.layers[0])?;
746    let is_discrete = discrete(shared.kind);
747
748    let mut labels: Vec<SharedString> = Vec::new();
749    let mut index: HashMap<SharedString, u32> = HashMap::new();
750    let mut read = Vec::with_capacity(chart.layers.len());
751    for (layer_index, layer) in chart.layers.iter().enumerate() {
752        let (along_channel, value_channel) = channels(layer)?;
753        if discrete(along_channel.kind) != is_discrete
754            || value_channel.kind != Kind::Quantitative
755            || (layer.mark == Mark::Bar && !is_discrete)
756        {
757            return None;
758        }
759        let column = layer.data.column(&along_channel.field)?;
760        let along = match (is_discrete, column) {
761            (true, column) => {
762                let domain = Discrete::of(column, &along_channel.sort);
763                let global: Vec<u32> = domain
764                    .labels
765                    .into_iter()
766                    .map(|label| {
767                        *index.entry(label.clone()).or_insert_with(|| {
768                            labels.push(label);
769                            labels.len() as u32 - 1
770                        })
771                    })
772                    .collect();
773                Along::Discrete(
774                    domain
775                        .codes
776                        .iter()
777                        .map(|&code| match code {
778                            MISSING => MISSING,
779                            code => global[code as usize],
780                        })
781                        .collect(),
782                )
783            }
784            (false, Column::Number(values)) => Along::Continuous(values.clone()),
785            (false, Column::Text(_)) => return None,
786        };
787        let values = match layer.data.column(&value_channel.field)? {
788            Column::Number(values) => values,
789            Column::Text(_) => return None,
790        };
791        read.push(Read {
792            index: layer_index,
793            mark: layer.mark,
794            along,
795            values,
796            sums: Vec::new(),
797        });
798    }
799
800    let count = labels.len();
801    let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
802    for layer in &mut read {
803        let rows = (0..layer.values.len())
804            .filter(|&row| layer.along.valid(row) && colors.of_row(layer.index, row).is_some());
805        if layer.mark != Mark::Bar {
806            for row in rows.filter(|&row| !layer.values[row].is_nan()) {
807                (lo, hi) = (lo.min(layer.values[row]), hi.max(layer.values[row]));
808            }
809            continue;
810        }
811        let Along::Discrete(codes) = &layer.along else {
812            return None;
813        };
814        let mut sums = vec![f64::NAN; count * colors.len()];
815        for row in rows {
816            let (v, color) = (layer.values[row], colors.of_row(layer.index, row)?);
817            if v.is_nan() {
818                continue;
819            }
820            let sum = &mut sums[codes[row] as usize * colors.len() + color];
821            *sum = if sum.is_nan() { v } else { *sum + v };
822        }
823        for stack in sums.chunks(colors.len().max(1)) {
824            let up: f64 = stack.iter().filter(|v| **v > 0.0).sum();
825            let down: f64 = stack.iter().filter(|v| **v < 0.0).sum();
826            (lo, hi) = (lo.min(down.min(0.0)), hi.max(up.max(0.0)));
827        }
828        layer.sums = sums;
829    }
830    let (lo, hi) = if lo > hi { (0.0, 0.0) } else { (lo, hi) };
831
832    let barred = read.iter().any(|layer| layer.mark == Mark::Bar);
833    let padding = if barred { BAND_PADDING } else { 0.0 };
834    let (plot, axes, value_scale, place): (Rect, Vec<Axis>, Linear, Place) = if horizontal {
835        let measured = layout.measured(&labels);
836        let plot = layout.plot(widest(&measured))?;
837        let band = Band {
838            count,
839            range: (plot.y, plot.bottom()),
840            padding,
841        };
842        let centers: Vec<f32> = (0..count).map(|b| band.center(b)).collect();
843        let left = layout.left(measured, &centers, plot, false);
844        let ticks = continuous(&value, lo, hi, x_count(plot));
845        let scale = Linear {
846            domain: ticks.domain,
847            range: (plot.x, plot.right()),
848        };
849        let at: Vec<f32> = ticks.values.iter().map(|&v| scale.at(v)).collect();
850        let axes = vec![layout.bottom(&ticks.labels, &at, plot, true), left];
851        (plot, axes, scale, Box::new(move |_: &Along, _| 0.0))
852    } else {
853        let count_y = (layout.plot_height() / Y_SPACING).floor().max(2.0) as usize;
854        let ticks = continuous(&value, lo, hi, count_y);
855        let measured = layout.measured(&ticks.labels);
856        let plot = layout.plot(widest(&measured))?;
857        let scale = Linear {
858            domain: ticks.domain,
859            range: (plot.bottom(), plot.y),
860        };
861        let at: Vec<f32> = ticks.values.iter().map(|&v| scale.at(v)).collect();
862        let left = layout.left(measured, &at, plot, true);
863        let (bottom, place): (Axis, Place) = if is_discrete {
864            let band = Band {
865                count,
866                range: (plot.x, plot.right()),
867                padding,
868            };
869            let centers: Vec<f32> = (0..count).map(|b| band.center(b)).collect();
870            let axis = layout.bottom(&labels, &centers, plot, false);
871            (
872                axis,
873                Box::new(move |along: &Along, row| match along {
874                    Along::Discrete(codes) => band.center(codes[row] as usize),
875                    Along::Continuous(_) => f32::NAN,
876                }),
877            )
878        } else {
879            let (lo, hi) = read
880                .iter()
881                .flat_map(|layer| {
882                    (0..layer.values.len())
883                        .filter(|&row| layer.along.valid(row))
884                        .filter_map(|row| match &layer.along {
885                            Along::Continuous(values) => Some(values[row]),
886                            Along::Discrete(_) => None,
887                        })
888                })
889                .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
890                    (lo.min(v), hi.max(v))
891                });
892            let (lo, hi) = if lo > hi { (0.0, 0.0) } else { (lo, hi) };
893            let ticks = continuous(&shared, lo, hi, x_count(plot));
894            let scale = Linear {
895                domain: ticks.domain,
896                range: (plot.x, plot.right()),
897            };
898            let at: Vec<f32> = ticks.values.iter().map(|&v| scale.at(v)).collect();
899            let axis = layout.bottom(&ticks.labels, &at, plot, false);
900            (
901                axis,
902                Box::new(move |along: &Along, row| match along {
903                    Along::Continuous(values) => scale.at(values[row]),
904                    Along::Discrete(_) => f32::NAN,
905                }),
906            )
907        };
908        (plot, vec![left, bottom], scale, place)
909    };
910
911    let band_axis = match horizontal {
912        true => (plot.y, plot.bottom()),
913        false => (plot.x, plot.right()),
914    };
915    let band = Band {
916        count,
917        range: band_axis,
918        padding,
919    };
920    let base = value_scale.at(0.0f64.clamp(
921        value_scale.domain.0.min(value_scale.domain.1),
922        value_scale.domain.0.max(value_scale.domain.1),
923    ));
924    let columns = plot.w.ceil() as usize;
925    let mut series = Vec::new();
926    let mut hover = Hover::default();
927    for layer in &read {
928        let name = |color: usize| colors.names[color].clone();
929        if layer.mark == Mark::Bar {
930            let mut bars = vec![Vec::new(); colors.len()];
931            for (b, stack) in layer.sums.chunks(colors.len().max(1)).enumerate() {
932                let (mut up, mut down) = (0.0, 0.0);
933                for (color, &v) in stack.iter().enumerate() {
934                    if v.is_nan() || v == 0.0 {
935                        continue;
936                    }
937                    let base = if v > 0.0 { &mut up } else { &mut down };
938                    let (from, to) = (value_scale.at(*base), value_scale.at(*base + v));
939                    *base += v;
940                    let (start, extent) = (band.start(b), band.width());
941                    bars[color].push(match horizontal {
942                        false => Rect {
943                            x: start,
944                            y: from.min(to),
945                            w: extent,
946                            h: (to - from).abs(),
947                        },
948                        true => Rect {
949                            x: from.min(to),
950                            y: start,
951                            w: (to - from).abs(),
952                            h: extent,
953                        },
954                    });
955                }
956            }
957            let bands = hover.bands.get_or_insert_with(|| Bands {
958                band,
959                vertical: !horizontal,
960                across: match horizontal {
961                    true => (plot.x, plot.right()),
962                    false => (plot.y, plot.bottom()),
963                },
964                labels: labels.clone(),
965                values: Vec::new(),
966            });
967            for (color, bars) in bars.into_iter().enumerate() {
968                if bars.is_empty() {
969                    continue;
970                }
971                let values = (0..count)
972                    .map(|b| layer.sums[b * colors.len() + color])
973                    .collect();
974                bands.values.push((color, values));
975                series.push(Series {
976                    name: name(color),
977                    color,
978                    mark: Mark::Bar,
979                    marks: Marks::Bars(bars),
980                });
981            }
982            continue;
983        }
984
985        let mut points: Vec<Vec<([f32; 2], u32)>> = vec![Vec::new(); colors.len()];
986        for row in (0..layer.values.len()).filter(|&row| layer.along.valid(row)) {
987            let Some(color) = colors.of_row(layer.index, row) else {
988                continue;
989            };
990            let v = layer.values[row];
991            let y = if v.is_nan() {
992                f32::NAN
993            } else {
994                value_scale.at(v)
995            };
996            points[color].push(([place(&layer.along, row), y], row as u32));
997        }
998        for (color, mut points) in points.into_iter().enumerate() {
999            if points.is_empty() {
1000                continue;
1001            }
1002            if !points.is_sorted_by(|a, b| a.0[0] <= b.0[0]) {
1003                points.sort_by(|a, b| a.0[0].total_cmp(&b.0[0]));
1004            }
1005            let runs = || {
1006                points
1007                    .split(|(p, _)| p[1].is_nan())
1008                    .filter(|run| !run.is_empty())
1009                    .map(|run| {
1010                        let run: Vec<[f32; 2]> = run.iter().map(|(p, _)| *p).collect();
1011                        if run.len() > columns {
1012                            decimate::m4(&run)
1013                        } else {
1014                            run
1015                        }
1016                    })
1017            };
1018            let marks = match layer.mark {
1019                Mark::Line => Marks::Lines(runs().collect()),
1020                Mark::Area => Marks::Areas(
1021                    runs()
1022                        .map(|mut run| {
1023                            let (first, last) = (run[0][0], run[run.len() - 1][0]);
1024                            run.extend([[last, base], [first, base]]);
1025                            run
1026                        })
1027                        .collect(),
1028                ),
1029                _ => {
1030                    let mut seen = HashSet::new();
1031                    let dense = points.len() > columns;
1032                    Marks::Points(
1033                        points
1034                            .iter()
1035                            .filter(|(p, _)| !p[1].is_nan())
1036                            .filter(|(p, _)| {
1037                                !dense || seen.insert((p[0].round() as i32, p[1].round() as i32))
1038                            })
1039                            .map(|(p, _)| *p)
1040                            .collect(),
1041                    )
1042                }
1043            };
1044            series.push(Series {
1045                name: name(color),
1046                color,
1047                mark: layer.mark,
1048                marks,
1049            });
1050            points.retain(|(p, _)| !p[1].is_nan());
1051            hover.traces.push(Trace {
1052                layer: layer.index,
1053                color,
1054                points,
1055            });
1056        }
1057    }
1058    Some((plot, axes, series, hover))
1059}
1060
1061/// About one tick to [`X_SPACING`] across `plot`.
1062fn x_count(plot: Rect) -> usize {
1063    (plot.w / X_SPACING).floor().max(2.0) as usize
1064}
1065
1066fn arcs(layer: &Layer, colors: &Colors, layout: &Layout) -> Option<Body> {
1067    let theta = layer.encoding.theta.as_ref()?;
1068    if theta.kind != Kind::Quantitative {
1069        return None;
1070    }
1071    let Column::Number(values) = layer.data.column(&theta.field)? else {
1072        return None;
1073    };
1074    let room = Rect {
1075        x: PAD,
1076        y: layout.top,
1077        w: layout.size[0] - 2.0 * PAD,
1078        h: layout.size[1] - layout.top - PAD,
1079    };
1080    let radius = room.w.min(room.h) / 2.0;
1081    if radius * 2.0 < MIN_PLOT {
1082        return None;
1083    }
1084    let center = [room.x + room.w / 2.0, room.y + room.h / 2.0];
1085
1086    let parts: Vec<(usize, f64)> = match colors.layers[0] {
1087        LayerColor::Field(_) => {
1088            let mut sums = vec![0.0; colors.len()];
1089            for (row, &v) in values.iter().enumerate() {
1090                if let Some(color) = colors.of_row(0, row)
1091                    && v > 0.0
1092                {
1093                    sums[color] += v;
1094                }
1095            }
1096            sums.into_iter()
1097                .enumerate()
1098                .filter(|(_, v)| *v > 0.0)
1099                .collect()
1100        }
1101        LayerColor::Fixed(color) => values
1102            .iter()
1103            .filter(|v| **v > 0.0)
1104            .map(|&v| (color, v))
1105            .collect(),
1106    };
1107    let total: f64 = parts.iter().map(|(_, v)| v).sum();
1108    let mut by_color: Vec<Vec<Slice>> = vec![Vec::new(); colors.len()];
1109    let mut slices = Vec::new();
1110    let mut start = 0.0f64;
1111    for (color, value) in parts {
1112        let end = start + value / total;
1113        let slice = Slice {
1114            center,
1115            radius,
1116            start: start as f32 * TAU,
1117            end: end as f32 * TAU,
1118        };
1119        by_color[color].push(slice);
1120        slices.push((color, slice, value));
1121        start = end;
1122    }
1123    let plot = Rect {
1124        x: center[0] - radius,
1125        y: center[1] - radius,
1126        w: radius * 2.0,
1127        h: radius * 2.0,
1128    };
1129    let series = by_color
1130        .into_iter()
1131        .enumerate()
1132        .filter(|(_, slices)| !slices.is_empty())
1133        .map(|(color, slices)| Series {
1134            name: colors.names[color].clone(),
1135            color,
1136            mark: Mark::Arc,
1137            marks: Marks::Slices(slices),
1138        })
1139        .collect();
1140    let hover = Hover {
1141        slices: Some(Slices { slices, total }),
1142        ..Hover::default()
1143    };
1144    Some((plot, Vec::new(), series, hover))
1145}