1use crate::abbreviate::find_abbreviations;
2use crate::aggregate::{aggregate_apply, minimal_precision_string};
3use crate::render::{Alignment, Column, Columns, Grid, Row, Value};
4use crate::{DagChartConfig, Dimensions, Schema, View};
5use crate::{Flat, Render};
6use std::collections::{HashMap, HashSet};
7use std::fmt::{Debug, Display};
8use std::hash::Hash;
9use std::marker::PhantomData;
10#[allow(unused_imports)]
12use crate::PathChart;
13
14pub struct DagChart<'a, S, V>
47where
48 S: Schema,
49 V: View<S>,
50{
51 view: &'a V,
52 _phantom: PhantomData<S>,
53}
54
55impl<'a, S, V> DagChart<'a, S, V>
56where
57 S: Schema,
58 V: View<S>,
59 <V as View<S>>::PrimaryDimension: Clone + PartialEq + Eq + Hash,
60 <V as View<S>>::BreakdownDimension: Clone + Display + PartialEq + Eq + Hash + Ord,
61 <V as View<S>>::DisplayDimensions: Clone + PartialEq + Eq + Hash + Ord,
62{
63 pub fn new(view: &'a V) -> Self {
65 Self {
66 view,
67 _phantom: PhantomData::default(),
68 }
69 }
70
71 pub fn render(self, config: Render<DagChartConfig>) -> Flat {
73 let mut aggregate_values: HashMap<(V::PrimaryDimension, V::BreakdownDimension), Vec<f64>> =
74 HashMap::default();
75 let mut partial_aggregate_values: HashMap<String, Vec<f64>> = HashMap::default();
76 let mut full_paths: HashSet<String> = HashSet::default();
77 let mut display_dimensions: Vec<V::DisplayDimensions> = Vec::default();
78 let mut sort_breakdowns: Vec<V::BreakdownDimension> = Vec::default();
79 let mut lookup: HashMap<
80 V::DisplayDimensions,
81 (V::PrimaryDimension, V::BreakdownDimension),
82 > = HashMap::default();
83 let mut dimension_values: Vec<HashSet<String>> = (0..self.view.display_headers().len())
84 .map(|_| HashSet::default())
85 .collect();
86 let mut path_occurrences: HashMap<String, usize> = HashMap::default();
87
88 for dims in self.view.dataset().data() {
89 let value = self.view.value(dims);
90 let primary_dim = self.view.primary_dim(dims);
91 let breakdown_dims = self.view.breakdown_dim(dims);
92 let aggregate_dims = (primary_dim.clone(), breakdown_dims.clone());
93 let display_dims = self.view.display_dims(dims);
94 let full_path = display_dims
95 .as_strings()
96 .iter()
97 .fold(String::default(), |acc, part| acc + part + ";");
98
99 for (j, value) in display_dims.as_strings().into_iter().enumerate() {
100 dimension_values[j].insert(value);
101 }
102
103 if !full_paths.contains(&full_path) {
111 full_paths.insert(full_path);
112
113 for dag_index in 0..display_dims.len() {
114 let partial_path = display_dims.as_strings()[0..dag_index + 1]
115 .iter()
116 .fold(String::default(), |acc, part| acc + part + ";");
117 path_occurrences
118 .entry(partial_path)
119 .and_modify(|c| *c += 1)
120 .or_insert(1);
121 }
122 }
123
124 if config.widget_config.show_aggregate {
125 for dag_index in 1..display_dims.len() {
126 let partial_path = display_dims.as_strings()[0..dag_index + 1]
127 .iter()
128 .fold(String::default(), |acc, part| acc + part + ";");
129 let values = partial_aggregate_values.entry(partial_path).or_default();
130 values.push(value);
131 }
132 }
133
134 let values = aggregate_values.entry(aggregate_dims.clone()).or_default();
135 values.push(value);
136
137 if !lookup.contains_key(&display_dims) {
138 lookup.insert(
142 display_dims.clone(),
143 (primary_dim.clone(), breakdown_dims.clone()),
144 );
145 display_dimensions.push(display_dims);
146 }
147
148 if !sort_breakdowns.contains(&breakdown_dims) {
149 sort_breakdowns.push(breakdown_dims);
150 }
151 }
152
153 display_dimensions.sort();
154 sort_breakdowns.sort();
155
156 let mut dimension_abbreviations: Vec<HashMap<String, String>> =
157 (0..self.view.display_headers().len())
158 .map(|_| HashMap::default())
159 .collect();
160
161 if config.widget_config.abbreviate {
162 let headers = self.view.display_headers();
163 let max_header_length = headers.iter().map(|h| h.chars().count()).max().unwrap();
164
165 for (dag_index, values) in dimension_values.iter().enumerate() {
166 let min_header_length = headers[dag_index].to_string().chars().count();
167 let (_, abbreviations) =
168 find_abbreviations(min_header_length, max_header_length, values);
169 dimension_abbreviations[dag_index] = abbreviations;
170 }
171 }
172
173 let mut columns = Columns::default();
174
175 for j in 0..self.view.display_headers().len() {
176 columns.push(Column::string(Alignment::Left));
178
179 if j + 1 < self.view.display_headers().len() {
180 columns.push(Column::string(Alignment::Center));
182
183 if config.widget_config.show_aggregate {
184 columns.push(Column::string(Alignment::Left));
186 columns.push(Column::string(Alignment::Right));
188 columns.push(Column::string(Alignment::Left));
190 }
191
192 columns.push(Column::string(Alignment::Center));
194 }
195 }
196
197 if config.show_aggregate {
198 columns.push(Column::string(Alignment::Center));
200 columns.push(Column::string(Alignment::Left));
202 columns.push(Column::string(Alignment::Right));
204 columns.push(Column::string(Alignment::Left));
206 }
207
208 columns.push(Column::string(Alignment::Center));
210 columns.push(Column::string(Alignment::Center));
212
213 if self.view.breakdown_label().is_some() {
214 for i in 0..sort_breakdowns.len() {
215 columns.push(Column::breakdown(Alignment::Center));
217
218 if i + 1 < sort_breakdowns.len() {
219 columns.push(Column::string(Alignment::Left));
221 }
222 }
223
224 columns.push(Column::string(Alignment::Center));
226 } else {
227 columns.push(Column::count(Alignment::Left));
229 }
230
231 let mut grid = Grid::new(columns);
232
233 if let Some(breakdown_header) = self.view.breakdown_label() {
234 let value_label = self.view.value_label();
235
236 if value_label == breakdown_header {
237 let pre_header = build_preheader(
238 &config,
239 self.view.display_headers().len(),
240 &breakdown_header,
241 true,
242 );
243 grid.add(pre_header);
244 } else {
245 let pre_header1 = build_preheader(
246 &config,
247 self.view.display_headers().len(),
248 &breakdown_header,
249 false,
250 );
251 grid.add(pre_header1);
252 let pre_header2 = build_preheader(
253 &config,
254 self.view.display_headers().len(),
255 &value_label,
256 true,
257 );
258 grid.add(pre_header2);
259 }
260 }
261
262 let mut header = Row::default();
263
264 for (j, name) in self.view.display_headers().iter().rev().enumerate() {
265 header.push(Value::String(name.clone()));
266
267 if j + 1 < self.view.display_headers().len() {
268 header.push(Value::String(" ".to_string()));
269
270 if config.widget_config.show_aggregate {
271 header.push(Value::Overflow(config.aggregate.to_string()));
272 header.push(Value::Skip);
273 header.push(Value::Skip);
274 }
275
276 header.push(Value::String(" ".to_string()));
278 }
279 }
280
281 if config.show_aggregate {
282 header.push(Value::Empty);
283 header.push(Value::Overflow(config.aggregate.to_string()));
284 header.push(Value::Skip);
285 header.push(Value::Skip);
286 }
287
288 header.push(Value::String(" ".to_string()));
289 header.push(Value::String("|".to_string()));
290
291 if self.view.breakdown_label().is_some() {
292 for (k, breakdown_dim) in sort_breakdowns.iter().enumerate() {
293 header.push(Value::String(breakdown_dim.to_string()));
294
295 if k + 1 < sort_breakdowns.len() {
296 header.push(Value::String(" ".to_string()));
297 }
298 }
299
300 header.push(Value::String("|".to_string()));
301 } else {
302 header.push(Value::Plain(format!(
303 "{}({})",
304 config.aggregate.to_string(),
305 self.view.value_label()
306 )));
307 }
309
310 grid.add(header);
311 let mut column_groups: HashMap<usize, Group> = HashMap::default();
312 let mut minimum_value = f64::MAX;
313 let mut maximum_value = f64::MIN;
314
315 for display_dims in display_dimensions.iter() {
316 let path = display_dims.as_strings();
317 let mut column_chunks_reversed: Vec<Vec<Value>> = Vec::default();
318 let mut descendant_position = None;
319
320 #[allow(unused_doc_comments)]
321 for (dag_index, part) in path.clone().iter().enumerate() {
354 let j = path.len() - dag_index - 1;
355 let partial_path = path[0..dag_index + 1]
356 .iter()
357 .fold(String::default(), |acc, part| acc + part + ";");
358 let group = column_groups.entry(j).or_default();
359
360 if group.matches(&partial_path) {
361 group.increment();
362 } else {
363 group.swap(partial_path.clone());
364 }
365
366 let occurrences = path_occurrences[&partial_path];
367 let position = (occurrences as f64 / 2.0).ceil() as usize - 1;
368 let mut column_chunks = Vec::default();
369
370 let position = match position {
371 position if position > group.index => Position::Above,
372 position if position == group.index => Position::At,
373 _ => Position::Below,
374 };
375
376 if position == Position::At {
377 if config.widget_config.abbreviate {
378 column_chunks.push(Value::String(
379 dimension_abbreviations[dag_index][part].clone(),
380 ));
381 } else {
382 column_chunks.push(Value::String(part.clone()));
383 }
384
385 if dag_index == 0 {
386 let (primary_dim, breakdown_dim) = lookup
387 .get(display_dims)
388 .expect("sort dimensions must be mapped to dimensions");
389
390 if self.view.breakdown_label().is_some() {
391 let breakdown_values: Vec<f64> = sort_breakdowns
392 .iter()
393 .map(|breakdown_dim| {
394 let aggregate_dims =
395 (primary_dim.clone(), breakdown_dim.clone());
396 aggregate_apply(
397 &config.aggregate,
398 &aggregate_values,
399 &aggregate_dims,
400 &mut minimum_value,
401 &mut maximum_value,
402 )
403 })
404 .collect();
405
406 if config.show_aggregate {
407 column_chunks.push(Value::String(" ".to_string()));
408 column_chunks.push(Value::String("[".to_string()));
409 column_chunks.push(Value::String(minimal_precision_string(
410 config.aggregate.apply(breakdown_values.as_slice()),
411 )));
412 column_chunks.push(Value::String("]".to_string()));
413 }
414
415 column_chunks.push(Value::String(" ".to_string()));
416 column_chunks.push(Value::String("|".to_string()));
417
418 for (k, breakdown_value) in breakdown_values.iter().enumerate() {
419 column_chunks.push(Value::Value(*breakdown_value));
420
421 if k + 1 != breakdown_values.len() {
422 column_chunks.push(Value::String(" ".to_string()));
423 }
424 }
425
426 column_chunks.push(Value::String("|".to_string()));
427 } else {
428 let aggregate_dims = (primary_dim.clone(), breakdown_dim.clone());
429 let value = aggregate_apply(
430 &config.aggregate,
431 &aggregate_values,
432 &aggregate_dims,
433 &mut minimum_value,
434 &mut maximum_value,
435 );
436
437 if config.show_aggregate {
438 column_chunks.push(Value::String(" ".to_string()));
439 column_chunks.push(Value::String("[".to_string()));
440 column_chunks.push(Value::String(minimal_precision_string(value)));
441 column_chunks.push(Value::String("]".to_string()));
442 }
443
444 column_chunks.push(Value::String(" ".to_string()));
445 column_chunks.push(Value::String("|".to_string()));
446 column_chunks.push(Value::Value(value));
447
448 if value < minimum_value {
449 minimum_value = value;
450 }
451
452 if value > maximum_value {
453 maximum_value = value;
454 }
455 }
456 } else {
457 assert!(descendant_position.is_some());
458 column_chunks.push(Value::String(" ".to_string()));
459
460 if config.widget_config.show_aggregate {
461 let value = config
462 .aggregate
463 .apply(partial_aggregate_values[&partial_path].as_slice());
464 column_chunks.push(Value::String("[".to_string()));
465 column_chunks.push(Value::String(minimal_precision_string(value)));
466 column_chunks.push(Value::String("]".to_string()));
467 }
468
469 if let Some(desc_pos) = &descendant_position {
470 match desc_pos {
471 Position::Above => {
472 column_chunks.push(Value::String(format!("┐")));
473 }
474 Position::At => {
475 column_chunks.push(Value::String(format!("-")));
476 }
477 Position::Below => {
478 column_chunks.push(Value::String(format!("┘")));
479 }
480 }
481 }
482 }
483 } else if dag_index != 0 {
484 assert!(descendant_position.is_some());
485
486 if let Some(desc_pos) = &descendant_position {
487 match desc_pos {
488 Position::At => {
489 column_chunks.push(Value::Empty);
490 column_chunks.push(Value::String(" ".to_string()));
491
492 if config.widget_config.show_aggregate {
493 column_chunks.push(Value::Empty);
494 column_chunks.push(Value::Empty);
495 column_chunks.push(Value::Empty);
496 }
497 column_chunks.push(Value::String(format!("-")));
498 }
499 Position::Above | Position::Below => {
500 }
502 }
503 }
504 }
505
506 descendant_position.replace(position);
507 column_chunks_reversed.push(column_chunks);
508 }
509
510 let mut row = Row::default();
511
512 for column_chunks in column_chunks_reversed.into_iter().rev() {
513 for value in column_chunks.into_iter() {
514 row.push(value);
515 }
516 }
517
518 grid.add(row);
519 }
520
521 Flat::new(config, minimum_value..maximum_value, grid)
522 }
523}
524
525fn build_preheader(
526 config: &Render<DagChartConfig>,
527 columns: usize,
528 label: &str,
529 embed: bool,
530) -> Row {
531 let mut row = Row::default();
532
533 for j in 0..columns {
534 row.push(Value::Empty);
535
536 if j + 1 < columns {
537 row.push(Value::Empty);
538
539 if config.widget_config.show_aggregate {
540 row.push(Value::Empty);
541 row.push(Value::Empty);
542 row.push(Value::Empty);
543 }
544
545 row.push(Value::Empty);
546 }
547 }
548
549 if config.show_aggregate {
550 row.push(Value::Empty);
551 row.push(Value::Empty);
552 row.push(Value::Empty);
553 row.push(Value::Empty);
554 }
555
556 row.push(Value::Empty);
557 row.push(Value::Empty);
558
559 if embed {
560 row.push(Value::Plain(format!(
561 "{}({label})",
562 config.aggregate.to_string(),
563 )));
564 } else {
565 row.push(Value::Plain(format!("{label}")));
566 }
567
568 row
569}
570
571#[derive(Debug, PartialEq, Eq)]
572enum Position {
573 Above,
574 At,
575 Below,
576}
577
578#[derive(Debug)]
579struct Group {
580 locus: Option<String>,
581 index: usize,
582}
583
584impl Default for Group {
585 fn default() -> Self {
586 Self {
587 locus: None,
588 index: 0,
589 }
590 }
591}
592
593impl Group {
594 fn matches(&self, path: &String) -> bool {
595 match &self.locus {
596 Some(l) => l == path,
597 None => false,
598 }
599 }
600
601 fn swap(&mut self, locus: String) {
602 self.locus.replace(locus);
603 self.index = 0;
604 }
605
606 fn increment(&mut self) {
607 self.index += 1;
608 }
609}
610
611#[cfg(test)]
612mod tests {
613
614 #[cfg(feature = "primitive_impls")]
615 mod primitive_impls {
616 use crate::{DagChart, DagChartConfig, Render};
617 use crate::{DatasetBuilder, Schema1, Schema2, Schemas};
618
619 #[test]
620 fn empty() {
621 let schema: Schema1<i64> = Schemas::one("abc");
622 let builder = DatasetBuilder::new(schema).build();
623 let view = builder.reflect_1st();
624 let dagchart = DagChart::new(&view);
625 let flat = dagchart.render(Render::default());
626 assert_eq!(
627 format!("\n{}", flat.to_string()),
628 r#"
629abc |Sum(abc)"#
630 );
631 }
632
633 #[test]
634 fn zero() {
635 let schema: Schema1<i64> = Schemas::one("abc");
636 let dataset = DatasetBuilder::new(schema).add((0,)).build();
637 let view = dataset.reflect_1st();
638 let dagchart = DagChart::new(&view);
639 let flat = dagchart.render(Render::default());
640 assert_eq!(
641 format!("\n{}", flat.to_string()),
642 r#"
643abc |Sum(abc)
6440 |"#
645 );
646 }
647
648 #[test]
649 fn negatives_and_positives() {
650 let schema: Schema1<i64> = Schemas::one("abc");
651 let dataset = DatasetBuilder::new(schema)
652 .add((-1,))
653 .add((0,))
654 .add((1,))
655 .build();
656 let view = dataset.reflect_1st();
657 let dagchart = DagChart::new(&view);
658 let flat = dagchart.render(Render::default());
659 assert_eq!(
660 format!("\n{}", flat.to_string()),
661 r#"
662abc |Sum(abc)
663-1 |⊖
6640 |
6651 |*"#
666 );
667 }
668
669 #[test]
670 fn one_thousand() {
671 let schema: Schema1<i64> = Schemas::one("abc");
672 let mut builder = DatasetBuilder::new(schema);
673
674 for _ in 0..1_000 {
675 builder.update((1,));
676 }
677
678 let dataset = builder.build();
679 let view = dataset.reflect_1st();
680 let dagchart = DagChart::new(&view);
681 let flat = dagchart.render(Render::default());
682 assert_eq!(
683 format!("\n{}", flat.to_string()),
684 r#"
685abc |Sum(abc)
6861 |**********************************************************************************************************************************************************"#
687 );
688 }
689
690 #[test]
691 fn negative_one_thousand() {
692 let schema: Schema1<i64> = Schemas::one("abc");
693 let mut builder = DatasetBuilder::new(schema);
694
695 for _ in 0..1_000 {
696 builder.update((-1,));
697 }
698
699 let dataset = builder.build();
700 let view = dataset.reflect_1st();
701 let dagchart = DagChart::new(&view);
702 let flat = dagchart.render(Render::default());
703 assert_eq!(
704 format!("\n{}", flat.to_string()),
705 r#"
706abc |Sum(abc)
707-1 |⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖⊖"#
708 );
709 }
710
711 #[test]
712 fn breakdown() {
713 let schema: Schema2<u8, u8> = Schemas::two("abc", "something long");
714 let dataset = DatasetBuilder::new(schema)
715 .add((1, 2))
716 .add((2, 3))
717 .add((3, 4))
718 .build();
719 let view = dataset.breakdown_2nd();
720 let dagchart = DagChart::new(&view);
721 let flat = dagchart.render(Render::default());
722 assert_eq!(
723 format!("\n{}", flat.to_string()),
724 r#"
725 Sum(something long)
726abc | 2 3 4 |
7271 | ** |
7282 | *** |
7293 | ****|"#
730 );
731 }
732
733 #[test]
734 fn count_breakdown() {
735 let schema = Schemas::two("abc", "something long");
736 let dataset = DatasetBuilder::new(schema)
737 .add((1, 2))
738 .add((2, 3))
739 .add((3, 4))
740 .build();
741 let view = dataset.count_breakdown_2nd();
742 let dagchart = DagChart::new(&view);
743 let flat = dagchart.render(Render::default());
744 assert_eq!(
745 format!("\n{}", flat.to_string()),
746 r#"
747 something long
748 Sum(Count)
749abc |2 3 4|
7501 |* |
7512 | * |
7523 | *|"#
753 );
754 }
755
756 #[test]
757 fn depth_3_combo111() {
758 let schema = Schemas::three("A", "B", "C");
759 let dataset = DatasetBuilder::new(schema).add(("a1", "b1", "c1")).build();
760 let view = dataset.count();
761 let dagchart = DagChart::new(&view);
762 let flat = dagchart.render(Render {
763 show_aggregate: true,
764 widget_config: DagChartConfig {
765 show_aggregate: true,
766 ..DagChartConfig::default()
767 },
768 ..Render::default()
769 });
770 assert_eq!(
771 format!("\n{}", flat.to_string()),
772 r#"
773C Sum B Sum A Sum |Sum(Count)
774c1 [1] - b1 [1] - a1 [1] |*"#
775 );
776 }
777
778 #[test]
779 fn depth_3_combo211() {
780 let schema = Schemas::three("A", "B", "C");
781 let dataset = DatasetBuilder::new(schema)
782 .add(("a1", "b1", "c1"))
783 .add(("a1", "b1", "c2"))
784 .build();
785 let view = dataset.count();
786 let dagchart = DagChart::new(&view);
787 let flat = dagchart.render(Render {
788 show_aggregate: true,
789 widget_config: DagChartConfig {
790 show_aggregate: true,
791 ..DagChartConfig::default()
792 },
793 ..Render::default()
794 });
795 assert_eq!(
796 format!("\n{}", flat.to_string()),
797 r#"
798C Sum B Sum A Sum |Sum(Count)
799c1 [1] - b1 [2] - a1 [2] |**
800c2 [1] ┘"#
801 );
802 }
803
804 #[test]
805 fn depth_3_combo221() {
806 let schema = Schemas::three("A", "B", "C");
807 let dataset = DatasetBuilder::new(schema)
808 .add(("a1", "b1", "c1"))
809 .add(("a1", "b1", "c2"))
810 .add(("a1", "b2", "c2"))
811 .build();
812 let view = dataset.count();
813 let dagchart = DagChart::new(&view);
814 let flat = dagchart.render(Render {
815 show_aggregate: true,
816 widget_config: DagChartConfig {
817 show_aggregate: true,
818 ..DagChartConfig::default()
819 },
820 ..Render::default()
821 });
822 assert_eq!(
823 format!("\n{}", flat.to_string()),
824 r#"
825C Sum B Sum A Sum |Sum(Count)
826c1 [1] - b1 [2] ┐
827c2 [1] ┘ - a1 [3] |***
828c2 [1] - b2 [1] ┘"#
829 );
830 }
831
832 #[test]
833 fn depth_3_combo221x() {
834 let schema = Schemas::three("A", "B", "C");
835 let dataset = DatasetBuilder::new(schema)
836 .add(("a1", "b1", "c1"))
837 .add(("a1", "b1", "c2"))
838 .add(("a1", "b2", "c1"))
839 .build();
840 let view = dataset.count();
841 let dagchart = DagChart::new(&view);
842 let flat = dagchart.render(Render {
843 show_aggregate: true,
844 widget_config: DagChartConfig {
845 show_aggregate: true,
846 ..DagChartConfig::default()
847 },
848 ..Render::default()
849 });
850 assert_eq!(
851 format!("\n{}", flat.to_string()),
852 r#"
853C Sum B Sum A Sum |Sum(Count)
854c1 [1] - b1 [2] ┐
855c2 [1] ┘ - a1 [3] |***
856c1 [1] - b2 [1] ┘"#
857 );
858 }
859
860 #[test]
861 fn depth_3_combo311() {
862 let schema = Schemas::three("A", "B", "C");
863 let dataset = DatasetBuilder::new(schema)
864 .add(("a1", "b1", "c1"))
865 .add(("a1", "b1", "c2"))
866 .add(("a1", "b1", "c3"))
867 .build();
868 let view = dataset.count();
869 let dagchart = DagChart::new(&view);
870 let flat = dagchart.render(Render {
871 show_aggregate: true,
872 widget_config: DagChartConfig {
873 show_aggregate: true,
874 ..DagChartConfig::default()
875 },
876 ..Render::default()
877 });
878 assert_eq!(
879 format!("\n{}", flat.to_string()),
880 r#"
881C Sum B Sum A Sum |Sum(Count)
882c1 [1] ┐
883c2 [1] - b1 [3] - a1 [3] |***
884c3 [1] ┘"#
885 );
886 }
887
888 #[test]
889 fn depth_3_combo321() {
890 let schema = Schemas::three("A", "B", "C");
891 let dataset = DatasetBuilder::new(schema)
892 .add(("a1", "b1", "c1"))
893 .add(("a1", "b1", "c2"))
894 .add(("a1", "b1", "c3"))
895 .add(("a1", "b2", "c3"))
896 .build();
897 let view = dataset.count();
898 let dagchart = DagChart::new(&view);
899 let flat = dagchart.render(Render {
900 show_aggregate: true,
901 widget_config: DagChartConfig {
902 show_aggregate: true,
903 ..DagChartConfig::default()
904 },
905 ..Render::default()
906 });
907 assert_eq!(
908 format!("\n{}", flat.to_string()),
909 r#"
910C Sum B Sum A Sum |Sum(Count)
911c1 [1] ┐
912c2 [1] - b1 [3] - a1 [4] |****
913c3 [1] ┘
914c3 [1] - b2 [1] ┘"#
915 );
916 }
917
918 #[test]
919 fn depth_3_combo321x() {
920 let schema = Schemas::three("A", "B", "C");
921 let dataset = DatasetBuilder::new(schema)
922 .add(("a1", "b1", "c1"))
923 .add(("a1", "b1", "c2"))
924 .add(("a1", "b1", "c3"))
925 .add(("a1", "b2", "c2"))
926 .build();
927 let view = dataset.count();
928 let dagchart = DagChart::new(&view);
929 let flat = dagchart.render(Render {
930 show_aggregate: true,
931 widget_config: DagChartConfig {
932 show_aggregate: true,
933 ..DagChartConfig::default()
934 },
935 ..Render::default()
936 });
937 assert_eq!(
938 format!("\n{}", flat.to_string()),
939 r#"
940C Sum B Sum A Sum |Sum(Count)
941c1 [1] ┐
942c2 [1] - b1 [3] - a1 [4] |****
943c3 [1] ┘
944c2 [1] - b2 [1] ┘"#
945 );
946 }
947
948 #[test]
949 fn depth_3_combo321y() {
950 let schema = Schemas::three("A", "B", "C");
951 let dataset = DatasetBuilder::new(schema)
952 .add(("a1", "b1", "c1"))
953 .add(("a1", "b1", "c2"))
954 .add(("a1", "b1", "c3"))
955 .add(("a1", "b2", "c1"))
956 .build();
957 let view = dataset.count();
958 let dagchart = DagChart::new(&view);
959 let flat = dagchart.render(Render {
960 show_aggregate: true,
961 widget_config: DagChartConfig {
962 show_aggregate: true,
963 ..DagChartConfig::default()
964 },
965 ..Render::default()
966 });
967 assert_eq!(
968 format!("\n{}", flat.to_string()),
969 r#"
970C Sum B Sum A Sum |Sum(Count)
971c1 [1] ┐
972c2 [1] - b1 [3] - a1 [4] |****
973c3 [1] ┘
974c1 [1] - b2 [1] ┘"#
975 );
976 }
977
978 #[test]
979 fn depth_3_combo331() {
980 let schema = Schemas::three("A", "B", "C");
981 let dataset = DatasetBuilder::new(schema)
982 .add(("a1", "b1", "c1"))
983 .add(("a1", "b1", "c2"))
984 .add(("a1", "b1", "c3"))
985 .add(("a1", "b2", "c1"))
986 .add(("a1", "b3", "c1"))
987 .build();
988 let view = dataset.count();
989 let dagchart = DagChart::new(&view);
990 let flat = dagchart.render(Render {
991 show_aggregate: true,
992 widget_config: DagChartConfig {
993 show_aggregate: true,
994 ..DagChartConfig::default()
995 },
996 ..Render::default()
997 });
998 assert_eq!(
999 format!("\n{}", flat.to_string()),
1000 r#"
1001C Sum B Sum A Sum |Sum(Count)
1002c1 [1] ┐
1003c2 [1] - b1 [3] ┐
1004c3 [1] ┘ - a1 [5] |*****
1005c1 [1] - b2 [1] ┘
1006c1 [1] - b3 [1] ┘"#
1007 );
1008 }
1009 }
1010
1011 #[cfg(feature = "pointer_impls")]
1012 mod pointer_impls {
1013 use crate::{DagChart, Render};
1014 use crate::{DatasetBuilder, Schema2, Schemas};
1015 use ordered_float::OrderedFloat;
1016
1017 #[test]
1018 fn view2() {
1019 let schema: Schema2<i64, OrderedFloat<f64>> = Schemas::two("abc", "def");
1020 let dataset = DatasetBuilder::new(schema)
1021 .add((1, OrderedFloat(0.1)))
1022 .add((2, OrderedFloat(0.4)))
1023 .add((3, OrderedFloat(0.5)))
1024 .add((4, OrderedFloat(0.9)))
1025 .build();
1026 let view = dataset.view_2nd();
1027 let dagchart = DagChart::new(&view);
1028 let flat = dagchart.render(Render::default());
1029 assert_eq!(
1030 format!("\n{}", flat.to_string()),
1031 r#"
1032abc |Sum(def)
10331 |
10342 |
10353 |*
10364 |*"#
1037 );
1038
1039 let view = dataset.count();
1040 let dagchart = DagChart::new(&view);
1041 let flat = dagchart.render(Render::default());
1042 assert_eq!(
1043 format!("\n{}", flat.to_string()),
1044 r#"
1045def abc |Sum(Count)
10460.1 - 1 |*
10470.4 - 2 |*
10480.5 - 3 |*
10490.9 - 4 |*"#
1050 );
1051
1052 let view = dataset.breakdown_2nd();
1053 let dagchart = DagChart::new(&view);
1054 let flat = dagchart.render(Render::default());
1055 assert_eq!(
1056 format!("\n{}", flat.to_string()),
1057 r#"
1058 Sum(def)
1059abc |0.1 0.4 0.5 0.9|
10601 | |
10612 | |
10623 | * |
10634 | * |"#
1064 );
1065
1066 let view = dataset.count_breakdown_2nd();
1067 let dagchart = DagChart::new(&view);
1068 let flat = dagchart.render(Render::default());
1069 assert_eq!(
1070 format!("\n{}", flat.to_string()),
1071 r#"
1072 def
1073 Sum(Count)
1074abc |0.1 0.4 0.5 0.9|
10751 | * |
10762 | * |
10773 | * |
10784 | * |"#
1079 );
1080 }
1081 }
1082}