Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
aleics committed Sep 24, 2024
1 parent ab2df9d commit c8cd442
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 30 deletions.
11 changes: 7 additions & 4 deletions bruc-core/examples/svg-line-chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ async fn main() {
{
"name": "primary",
"values": [
{ "x": -3, "y": -10 },
{ "x": -2, "y": -20 },
{ "x": -1, "y": -30 },
{ "x": 0, "y": 0 },
{ "x": 1, "y": 50 },
{ "x": 2, "y": 15 },
{ "x": 3, "y": 30 }
{ "x": 1, "y": 100 },
{ "x": 2, "y": 200 },
{ "x": 3, "y": 300 }
]
}
],
"scales": [
{
"type": "linear",
"type": "log",
"name": "horizontal",
"domain": { "data": "primary", "field": "x" },
"range": [0, 500]
Expand Down
9 changes: 9 additions & 0 deletions bruc-core/src/graph/node/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use scale::LogOperator;
use shape::PieOperator;

use crate::graph::node::scale::{IdentityOperator, LinearOperator};
Expand Down Expand Up @@ -68,6 +69,7 @@ pub enum Operator {
DomainInterval(DomainIntervalOperator),
DomainDiscrete(DomainDiscreteOperator),
Linear(LinearOperator),
Log(LogOperator),
Band(BandOperator),
Identity(IdentityOperator),
}
Expand Down Expand Up @@ -137,6 +139,12 @@ impl Operator {
Operator::Linear(LinearOperator::new(range, field, output))
}

/// Create a new logarithmic `Operator` instance for a certain `range`, with a given `field` reference and an
/// `output` field name.
pub(crate) fn log(range: (f32, f32), field: &str, output: &str) -> Self {
Operator::Log(LogOperator::new(range, field, output))
}

pub(crate) fn band(range: (f32, f32), field: &str, output: &str) -> Self {
Operator::Band(BandOperator::new(range, field, output))
}
Expand Down Expand Up @@ -164,6 +172,7 @@ impl Operator {
Operator::DomainInterval(domain_interval) => domain_interval.evaluate(pulse).await,
Operator::DomainDiscrete(domain_discrete) => domain_discrete.evaluate(pulse).await,
Operator::Linear(linear) => linear.evaluate(pulse).await,
Operator::Log(log) => log.evaluate(pulse).await,
Operator::Band(band) => band.evaluate(pulse).await,
Operator::Identity(identity) => identity.evaluate(pulse).await,
}
Expand Down
101 changes: 100 additions & 1 deletion bruc-core/src/graph/node/scale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use bruc_expression::data::{DataItem, DataSource};

use crate::data::DataValue;

use crate::graph::node::util::normalize_log10;
use crate::graph::pulse::ResolvedDomain;
use crate::spec::scale::domain::Domain;

Expand Down Expand Up @@ -188,6 +189,75 @@ impl Evaluation for LinearOperator {
}
}

#[derive(Debug, PartialEq)]
pub struct LogOperator {
range: (f32, f32),
field: String,
output: String,
}

impl LogOperator {
/// Create a new `LinearOperator` instance.
pub(crate) fn new(range: (f32, f32), field: &str, output: &str) -> Self {
LogOperator {
range,
field: field.to_string(),
output: output.to_string(),
}
}

/// Apply the operator's logic by linearly scaling the referenced `field` and creating a new
/// `output` field.
fn apply(&self, values: &[DataValue], domain: (f32, f32)) -> Vec<DataValue> {
let mut result = values.to_vec();

// Iterate over the current series
for value in &mut result {
// Apply scale to field
let scale_result = value
.get_number(&self.field)
.map(|value| interpolate(normalize_log10(*value, domain), self.range));

println!("scale result {:?} -> {:?}", value, scale_result);

if let Some(scale_item) = scale_result {
// Add scale result to value with the scale's name
value.instance.clear();
value.insert(&self.output, DataItem::Number(scale_item));
}
}

result
}
}

impl Evaluation for LogOperator {
async fn evaluate_single(&self, _single: SinglePulse) -> Pulse {
panic!("Linear operator requires a multi-pulse with data and a domain values.")
}

async fn evaluate_multi(&self, multi: MultiPulse) -> Pulse {
let mut values = Vec::new();
let mut domain: Option<(f32, f32)> = None;

for pulse in multi.pulses {
match pulse {
SinglePulse::Data(data) => values.extend(data),
SinglePulse::Domain(ResolvedDomain::Interval(min, max)) => domain = Some((min, max)),
_ => continue,
}
}

if values.is_empty() {
return Pulse::data(Vec::new());
}

let domain = domain.expect("Domain pulse not provided for linear operator");

Pulse::data(self.apply(&values, domain))
}
}

/// `BandOperator` represents an operator of the graph, which maps a discrete domain to a
/// continuous range of values. `field` references the data source and `output` the name
/// of the new field with the result of the operator.
Expand Down Expand Up @@ -322,7 +392,7 @@ mod tests {

use crate::{
data::DataValue,
graph::{pulse::ResolvedDomain, Evaluation, Pulse, SinglePulse},
graph::{node::scale::LogOperator, pulse::ResolvedDomain, Evaluation, Pulse, SinglePulse},
spec::scale::domain::Domain,
};

Expand Down Expand Up @@ -491,4 +561,33 @@ mod tests {

assert_eq!(pulse, Pulse::Single(data))
}

#[tokio::test]
async fn log_applies_multi_pulse() {
let first_pulse = SinglePulse::Data(vec![
DataValue::from_pairs(vec![("a", 10.0.into()), ("b", 1.0.into())]),
DataValue::from_pairs(vec![("a", 100.0.into()), ("b", 1.0.into())]),
]);
let second_pulse = SinglePulse::Data(vec![
DataValue::from_pairs(vec![("a", 1000.0.into()), ("b", 1.0.into())]),
DataValue::from_pairs(vec![("a", 100000.0.into()), ("b", 1.0.into())]),
]);

let domain = SinglePulse::Domain(ResolvedDomain::Interval(10.0, 100000.0));

let operator = LogOperator::new((0.0, 600.0), "a", "x");
let pulse = operator
.evaluate(Pulse::multi(vec![first_pulse, second_pulse, domain]))
.await;

assert_eq!(
pulse,
Pulse::data(vec![
DataValue::from_pairs(vec![("x", 0.0.into())]),
DataValue::from_pairs(vec![("x", 150.0.into())]),
DataValue::from_pairs(vec![("x", 300.0.into())]),
DataValue::from_pairs(vec![("x", 600.0.into())]),
])
);
}
}
31 changes: 26 additions & 5 deletions bruc-core/src/graph/node/util.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
use core::f32;

pub(crate) fn normalize(x: f32, (min, max): (f32, f32)) -> f32 {
let x = x.clamp(min, max);
(x - min) / (max - min)
pub(crate) fn normalize(value: f32, (min, max): (f32, f32)) -> f32 {
let value = value.clamp(min, max);
(value - min) / (max - min)
}

pub(crate) fn interpolate(x: f32, (min, max): (f32, f32)) -> f32 {
(max - min) * x + min
pub(crate) fn normalize_by<F>(value: f32, (min, max): (f32, f32), by: F) -> f32
where
F: Fn(f32) -> f32,
{
normalize(by(value), (by(min), by(max)))
}

fn logn(value: f32) -> f32 {
if value > 0.0 {
value.log10()
} else if value < 0.0 {
-f32::log10(-value)
} else {
1e-10
}
}

pub(crate) fn normalize_log10(value: f32, domain: (f32, f32)) -> f32 {
normalize_by(value, domain, logn)
}

pub(crate) fn interpolate(value: f32, (min, max): (f32, f32)) -> f32 {
(max - min) * value + min
}

pub(crate) fn radians_to_degrees(radians: f32) -> f32 {
Expand Down
35 changes: 35 additions & 0 deletions bruc-core/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::graph::node::shape::{SceneWindow, PIE_OUTER_RADIUS_FIELD_NAME, PIE_VA
use crate::spec::axis::Axis;
use crate::spec::scale::band::BandScale;
use crate::spec::scale::linear::LinearScale;
use crate::spec::scale::log::LogScale;
use crate::spec::scale::range::Range;
use crate::spec::scale::{Scale, ScaleKind};
use crate::spec::shape::bar::BarShape;
Expand Down Expand Up @@ -283,6 +284,14 @@ impl Visitor {
data_node,
result,
),
ScaleKind::Log(log) => self.visit_log(
log,
scale.name.to_string(),
field,
output,
data_node,
result,
),
ScaleKind::Band(band) => self.visit_band(
band,
scale.name.to_string(),
Expand Down Expand Up @@ -320,6 +329,32 @@ impl Visitor {
linear_node
}

fn visit_log(
&self,
log: LogScale,
name: String,
field: &str,
output: &str,
data_node: usize,
result: &mut ParseResult,
) -> usize {
let domain_operator = Operator::domain_interval(log.domain.clone());
let domain_node = result.graph.add_node(domain_operator);

result.graph.add_edge(data_node, domain_node);
result.collection.domain.insert(name.clone(), domain_node);

let Range::Literal(range_min, range_max) = log.range;
let log_operator = Operator::log((range_min, range_max), field, output);
let log_node = result.graph.add_node(log_operator);

result.graph.add_edge(domain_node, log_node);
result.graph.add_edge(data_node, log_node);
result.collection.scales.insert(name, log_node);

log_node
}

fn visit_band(
&self,
band: BandScale,
Expand Down
77 changes: 77 additions & 0 deletions bruc-core/src/spec/scale/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use super::{domain::Domain, range::Range};

#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct LogScale {
#[cfg_attr(feature = "serde", serde(default = "Domain::default_literal"))]
pub(crate) domain: Domain,

#[cfg_attr(feature = "serde", serde(default = "Range::default_literal"))]
pub(crate) range: Range,
}

#[cfg(test)]
#[cfg(feature = "serde")]
mod serde_tests {
use crate::spec::scale::domain::Domain;
use crate::spec::scale::log::LogScale;
use crate::spec::scale::range::Range;

#[test]
fn deserialize_log_scale() {
let log_scale: LogScale = serde_json::from_str(
r#"{
"name": "x",
"domain": [0, 100],
"range": [0, 1]
}"#,
)
.unwrap();

assert_eq!(
log_scale,
LogScale {
domain: Domain::Literal(vec![0.0, 100.0]),
range: Range::Literal(0.0, 1.0)
}
)
}

#[test]
fn deserialize_log_scale_default_domain() {
let log_scale: LogScale = serde_json::from_str(
r#"{
"name": "x",
"range": [0, 1]
}"#,
)
.unwrap();

assert_eq!(
log_scale,
LogScale {
domain: Domain::Literal(vec![0.0, 1.0]),
range: Range::Literal(0.0, 1.0)
}
)
}

#[test]
fn deserialize_log_scale_default_range() {
let log_scale: LogScale = serde_json::from_str(
r#"{
"name": "x",
"domain": [0, 100]
}"#,
)
.unwrap();

assert_eq!(
log_scale,
LogScale {
domain: Domain::Literal(vec![0.0, 100.0]),
range: Range::Literal(0.0, 1.0)
}
)
}
}
Loading

0 comments on commit c8cd442

Please sign in to comment.