1use 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
25const PAD: f32 = 8.0;
27const GAP: f32 = 6.0;
29const SWATCH: f32 = 10.0;
30const LEGEND_GAP: f32 = 16.0;
32const X_SPACING: f32 = 80.0;
34const Y_SPACING: f32 = 40.0;
35const BAND_PADDING: f32 = 0.2;
36const MIN_PLOT: f32 = 16.0;
38const SAME_X: f32 = 1.0;
41
42const 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#[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#[derive(Clone, Debug, PartialEq)]
83pub struct Tick {
84 pub at: f32,
85 pub label: Option<Label>,
86}
87
88#[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#[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 Lines(Vec<Vec<[f32; 2]>>),
118 Areas(Vec<Vec<[f32; 2]>>),
120 Points(Vec<[f32; 2]>),
122 Slices(Vec<Slice>),
123}
124
125#[derive(Clone, Debug, PartialEq)]
127pub struct Series {
128 pub name: Option<SharedString>,
129 pub color: usize,
131 pub mark: Mark,
132 pub marks: Marks,
133}
134
135#[derive(Clone, Debug, PartialEq)]
137pub struct Tip {
138 pub title: SharedString,
139 pub rows: Vec<(usize, SharedString)>,
141 pub points: Vec<(usize, [f32; 2])>,
143 pub band: Option<Rect>,
145}
146
147#[derive(Clone, Debug)]
148pub struct Plan {
149 pub size: [f32; 2],
150 pub plot: Rect,
152 pub title: Option<Label>,
153 pub legend_title: Option<Label>,
155 pub legend: Vec<Swatch>,
156 pub axes: Vec<Axis>,
157 pub series: Vec<Series>,
159 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#[derive(Clone, Debug)]
175struct Bands {
176 band: Band,
177 vertical: bool,
178 across: (f32, f32),
179 labels: Vec<SharedString>,
180 values: Vec<(usize, Vec<f64>)>,
182}
183
184#[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
199pub 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 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 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
383fn 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
393struct Colors {
396 names: Vec<Option<SharedString>>,
397 layers: Vec<LayerColor>,
398}
399
400enum LayerColor {
401 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
460struct 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
531fn 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
543struct Layout<'a> {
545 size: [f32; 2],
546 line: f32,
547 top: f32,
548 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 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 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 fn plot_height(&self) -> f32 {
613 self.size[1] - self.top - PAD - GAP - self.line - self.reserved
614 }
615
616 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 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 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
689fn 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
703type Place = Box<dyn Fn(&Along, usize) -> f32>;
705
706enum Along {
708 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
722struct Read<'a> {
725 index: usize,
726 mark: Mark,
727 along: Along,
728 values: &'a Arc<[f64]>,
729 sums: Vec<f64>,
731}
732
733fn cartesian(chart: &Chart, colors: &Colors, layout: &mut Layout) -> Option<Body> {
734 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, ¢ers, 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, ¢ers, 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
1061fn 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}