Bar Chart baseline. (#2621)

Bar Chart ready.
This commit is contained in:
Andrés N. Robalino
2020-09-30 13:27:52 -05:00
committed by GitHub
parent 892a416211
commit a56abb6502
16 changed files with 803 additions and 136 deletions

View File

@ -1,6 +1,6 @@
#![allow(clippy::type_complexity)]
use crate::value::compute_values;
use crate::value::unsafe_compute_values;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
@ -23,6 +23,14 @@ impl Labels {
}
}
pub fn at_split(&self, idx: usize) -> Option<&str> {
if let Some(k) = self.y.get(idx) {
Some(&k[..])
} else {
None
}
}
pub fn grouped(&self) -> impl Iterator<Item = &String> {
self.x.iter()
}
@ -51,7 +59,7 @@ fn formula(
calculator: Box<dyn Fn(Vec<&Value>) -> Result<Value, ShellError> + Send + Sync + 'static>,
) -> Box<dyn Fn(&Value, Vec<&Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
Box::new(move |acc, datax| -> Result<Value, ShellError> {
let result = match compute_values(Operator::Multiply, &acc, &acc_begin) {
let result = match unsafe_compute_values(Operator::Multiply, &acc, &acc_begin) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
@ -62,43 +70,100 @@ fn formula(
};
match calculator(datax) {
Ok(total) => Ok(match compute_values(Operator::Plus, &result, &total) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned_unknown(),
right_type.spanned_unknown(),
))
}
}),
Ok(total) => Ok(
match unsafe_compute_values(Operator::Plus, &result, &total) {
Ok(v) => v.into_untagged_value(),
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
left_type.spanned_unknown(),
right_type.spanned_unknown(),
))
}
},
),
Err(reason) => Err(reason),
}
})
}
pub fn reducer_for(
command: Reduction,
command: &Reduction,
) -> Box<dyn Fn(&Value, Vec<&Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
match command {
Reduction::Accumulate => Box::new(formula(
UntaggedValue::int(1).into_untagged_value(),
Box::new(sum),
)),
_ => Box::new(formula(
Reduction::Count => Box::new(formula(
UntaggedValue::int(0).into_untagged_value(),
Box::new(sum),
)),
}
}
pub fn max(values: &Value, tag: impl Into<Tag>) -> Result<&Value, ShellError> {
pub fn max(values: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
values
.table_entries()
.filter_map(|dataset| dataset.table_entries().max())
.max()
.ok_or_else(|| ShellError::labeled_error("err", "err", &tag))
let mut x = UntaggedValue::int(0);
for split in values.table_entries() {
match split.value {
UntaggedValue::Table(ref values) => {
let inner = inner_max(values)?;
if let Ok(greater_than) =
crate::value::compare_values(Operator::GreaterThan, &inner.value, &x)
{
if greater_than {
x = inner.value.clone();
}
} else {
return Err(ShellError::unexpected(format!(
"Could not compare\nleft: {:?}\nright: {:?}",
inner.value, x
)));
}
}
_ => {
return Err(ShellError::labeled_error(
"Attempted to compute the sum of a value that cannot be summed.",
"value appears here",
split.tag.span,
))
}
}
}
Ok(x.into_value(&tag))
}
pub fn inner_max(data: &[Value]) -> Result<Value, ShellError> {
let mut biggest = data
.first()
.ok_or_else(|| {
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
})?
.value
.clone();
for value in data.iter() {
if let Ok(greater_than) =
crate::value::compare_values(Operator::GreaterThan, &value.value, &biggest)
{
if greater_than {
biggest = value.value.clone();
}
} else {
return Err(ShellError::unexpected(format!(
"Could not compare\nleft: {:?}\nright: {:?}",
biggest, value.value
)));
}
}
Ok(Value {
value: biggest,
tag: Tag::unknown(),
})
}
pub fn sum(data: Vec<&Value>) -> Result<Value, ShellError> {
@ -107,7 +172,7 @@ pub fn sum(data: Vec<&Value>) -> Result<Value, ShellError> {
for value in data {
match value.value {
UntaggedValue::Primitive(_) => {
acc = match compute_values(Operator::Plus, &acc, &value) {
acc = match unsafe_compute_values(Operator::Plus, &acc, &value) {
Ok(v) => v,
Err((left_type, right_type)) => {
return Err(ShellError::coerce_error(
@ -133,19 +198,12 @@ pub fn sort_columns(
values: &[String],
format: &Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
) -> Result<Vec<String>, ShellError> {
let mut keys = vec![];
let mut keys = values.to_vec();
if let Some(fmt) = format {
for k in values.iter() {
let k = k.clone().tagged_unknown();
let v = crate::value::Date::naive_from_str(k.borrow_tagged())?.into_untagged_value();
keys.push(fmt(&v, k.to_string())?);
}
} else {
keys = values.to_vec();
if format.is_none() {
keys.sort();
}
keys.sort();
Ok(keys)
}
@ -167,17 +225,13 @@ pub fn sort(planes: &Labels, values: &Value, tag: impl Into<Tag>) -> Result<Valu
let grouped = groups.get_data_by_key(key.borrow_spanned());
if let Some(grouped) = grouped {
y.push(grouped.table_entries().cloned().collect::<Vec<_>>());
y.push(grouped);
} else {
let empty = UntaggedValue::table(&[]).into_value(&tag);
y.push(empty.table_entries().cloned().collect::<Vec<_>>());
y.push(UntaggedValue::Table(vec![]).into_value(&tag));
}
}
x.push(
UntaggedValue::table(&y.iter().cloned().flatten().collect::<Vec<Value>>())
.into_value(&tag),
);
x.push(UntaggedValue::table(&y).into_value(&tag));
}
Ok(UntaggedValue::table(&x).into_value(&tag))
@ -195,17 +249,27 @@ pub fn evaluate(
let mut y = vec![];
for (idx, subset) in split.table_entries().enumerate() {
let mut set = vec![];
if let UntaggedValue::Table(values) = &subset.value {
if let Some(ref evaluator) = evaluator {
let mut evaluations = vec![];
if let Some(ref evaluator) = evaluator {
let value = evaluator(idx, subset)?;
for set in values.iter() {
evaluations.push(evaluator(idx, set)?);
}
set.push(value);
} else {
set.push(UntaggedValue::int(1).into_value(&tag));
y.push(UntaggedValue::Table(evaluations).into_value(&tag));
} else {
y.push(
UntaggedValue::Table(
values
.iter()
.map(|_| UntaggedValue::int(1).into_value(&tag))
.collect::<Vec<_>>(),
)
.into_value(&tag),
);
}
}
y.push(UntaggedValue::table(&set).into_value(&tag));
}
x.push(UntaggedValue::table(&y).into_value(&tag));
@ -215,14 +279,17 @@ pub fn evaluate(
}
pub enum Reduction {
#[allow(dead_code)]
Count,
Accumulate,
}
pub fn reduce(values: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
pub fn reduce(
values: &Value,
reduction_with: &Reduction,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let reduce_with = reducer_for(Reduction::Accumulate);
let reduce_with = reducer_for(reduction_with);
let mut datasets = vec![];
for dataset in values.table_entries() {
@ -255,8 +322,8 @@ pub fn percentages(
.filter_map(|s| {
let hundred = UntaggedValue::decimal_from_float(100.0, tag.span);
match compute_values(Operator::Divide, &hundred, &maxima) {
Ok(v) => match compute_values(Operator::Multiply, &s, &v) {
match unsafe_compute_values(Operator::Divide, &hundred, &maxima) {
Ok(v) => match unsafe_compute_values(Operator::Multiply, &s, &v) {
Ok(v) => Some(v.into_untagged_value()),
Err(_) => None,
},

View File

@ -6,6 +6,7 @@ mod internal;
pub use crate::utils::group::group;
pub use crate::utils::split::split;
pub use crate::utils::internal::Reduction;
use crate::utils::internal::*;
use derive_new::new;
@ -27,8 +28,9 @@ pub struct Model {
pub struct Operation<'a> {
pub grouper: Option<Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send>>,
pub splitter: Option<Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send>>,
pub format: Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
pub format: &'a Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
pub eval: &'a Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
pub reduction: &'a Reduction,
}
pub fn report(
@ -46,19 +48,17 @@ pub fn report(
.map(|(key, _)| key.clone())
.collect::<Vec<_>>();
let x = if options.format.is_some() {
sort_columns(&x, &options.format)
} else {
sort_columns(&x, &None)
}?;
let x = sort_columns(&x, &options.format)?;
let mut y = splitted
.row_entries()
.map(|(key, _)| key.clone())
.collect::<Vec<_>>();
y.sort();
let planes = Labels { x, y };
let sorted = sort(&planes, &splitted, &tag)?;
let evaluated = evaluate(
@ -72,11 +72,11 @@ pub fn report(
)?;
let group_labels = planes.grouping_total();
let split_labels = planes.splits_total();
let reduced = reduce(&evaluated, &tag)?;
let reduced = reduce(&evaluated, options.reduction, &tag)?;
let max = max(&reduced, &tag)?.clone();
let maxima = max.clone();
let maxima = max(&reduced, &tag)?;
let percents = percentages(&maxima, &reduced, &tag)?;
@ -89,7 +89,7 @@ pub fn report(
},
Range {
start: UntaggedValue::int(0).into_untagged_value(),
end: max,
end: split_labels,
},
),
data: reduced,
@ -99,7 +99,6 @@ pub fn report(
pub mod helpers {
use super::Model;
use bigdecimal::BigDecimal;
use indexmap::indexmap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
@ -113,10 +112,6 @@ pub mod helpers {
UntaggedValue::int(s).into_untagged_value()
}
pub fn decimal(f: impl Into<BigDecimal>) -> Value {
UntaggedValue::decimal(f.into()).into_untagged_value()
}
pub fn decimal_from_float(f: f64, span: Span) -> Value {
UntaggedValue::decimal_from_float(f, span).into_untagged_value()
}
@ -216,9 +211,12 @@ pub mod helpers {
}
pub fn date_formatter(
fmt: &'static str,
fmt: String,
) -> Box<dyn Fn(&Value, String) -> Result<String, ShellError>> {
Box::new(move |date: &Value, _: String| date.format(&fmt))
Box::new(move |date: &Value, _: String| {
let fmt = fmt.clone();
date.format(&fmt)
})
}
pub fn assert_without_checking_percentages(report_a: Model, report_b: Model) {
@ -232,23 +230,24 @@ pub mod helpers {
#[cfg(test)]
mod tests {
use super::helpers::{
assert_without_checking_percentages, committers, date_formatter, decimal,
decimal_from_float, int, table,
assert_without_checking_percentages, committers, date_formatter, decimal_from_float, int,
table,
};
use super::{report, Labels, Model, Operation, Range};
use super::{report, Labels, Model, Operation, Range, Reduction};
use nu_errors::ShellError;
use nu_protocol::Value;
use nu_source::{Span, Tag, TaggedItem};
use nu_value_ext::ValueExt;
#[test]
fn prepares_report_using_accumulating_value() {
fn prepares_report_using_counting_value() {
let committers = table(&committers());
let by_date = Box::new(move |_, row: &Value| {
let key = String::from("date").tagged_unknown();
let key = row.get_data_by_key(key.borrow_spanned()).unwrap();
let callback = date_formatter("%Y-%m-%d");
let callback = date_formatter("%Y-%m-%d".to_string());
callback(&key, "nothing".to_string())
});
@ -261,7 +260,7 @@ mod tests {
let options = Operation {
grouper: Some(by_date),
splitter: Some(by_country),
format: Some(date_formatter("%Y-%m-%d")),
format: &None,
eval: /* value to be used for accumulation */ &Some(Box::new(move |_, value: &Value| {
let chickens_key = String::from("chickens").tagged_unknown();
@ -275,6 +274,7 @@ mod tests {
)
})
})),
reduction: &Reduction::Count
};
assert_without_checking_percentages(
@ -295,29 +295,29 @@ mod tests {
},
Range {
start: int(0),
end: int(60),
end: int(3),
},
),
data: table(&[
table(&[int(10), int(30), int(60)]),
table(&[int(5), int(15), int(30)]),
table(&[int(2), int(6), int(12)]),
table(&[int(10), int(20), int(30)]),
table(&[int(5), int(10), int(15)]),
table(&[int(2), int(4), int(6)]),
]),
percentages: table(&[
table(&[
decimal_from_float(33.33, Span::unknown()),
decimal_from_float(66.66, Span::unknown()),
decimal_from_float(99.99, Span::unknown()),
]),
table(&[
decimal_from_float(16.66, Span::unknown()),
decimal(50),
decimal(100),
decimal_from_float(33.33, Span::unknown()),
decimal_from_float(49.99, Span::unknown()),
]),
table(&[
decimal_from_float(8.33, Span::unknown()),
decimal(25),
decimal(50),
]),
table(&[
decimal_from_float(3.33, Span::unknown()),
decimal(10),
decimal(20),
decimal_from_float(6.66, Span::unknown()),
decimal_from_float(13.33, Span::unknown()),
decimal_from_float(19.99, Span::unknown()),
]),
]),
},

View File

@ -16,7 +16,7 @@ pub fn split(
let mut out = TaggedDictBuilder::new(&tag);
if splitter.is_none() {
out.insert_untagged("table", UntaggedValue::table(&[value.clone()]));
out.insert_untagged("table", value.clone());
return Ok(out.into_value());
}