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 a300b19
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 48 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
6 changes: 4 additions & 2 deletions bruc-core/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ impl Edge {

#[cfg(test)]
mod tests {
use node::scale::ScaleType;

use crate::graph::node::shape::SceneWindow;
use crate::spec::axis::{Axis, AxisOrientation};
use crate::spec::scale::domain::Domain;
Expand Down Expand Up @@ -321,7 +323,7 @@ mod tests {
);

let x_domain = graph.add(
Operator::domain_interval(Domain::Literal(vec![0.0, 20.0])),
Operator::domain_interval(Domain::Literal(vec![0.0, 20.0]), ScaleType::Linear),
vec![filter],
);

Expand All @@ -331,7 +333,7 @@ mod tests {
);

let y_domain = graph.add(
Operator::domain_interval(Domain::Literal(vec![0.0, 20.0])),
Operator::domain_interval(Domain::Literal(vec![0.0, 20.0]), ScaleType::Linear),
vec![filter],
);

Expand Down
13 changes: 11 additions & 2 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, ScaleType};
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,12 +139,18 @@ 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))
}

pub(crate) fn domain_interval(domain: Domain) -> Self {
Operator::DomainInterval(DomainIntervalOperator::new(domain))
pub(crate) fn domain_interval(domain: Domain, scale: ScaleType) -> Self {
Operator::DomainInterval(DomainIntervalOperator::new(domain, scale))
}

pub(crate) fn domain_discrete(domain: Domain, outer_padding: bool) -> Self {
Expand All @@ -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
140 changes: 128 additions & 12 deletions 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 All @@ -14,11 +15,12 @@ pub(crate) const SCALE_BAND_BANDWIDTH_FIELD_NAME: &str = "bandwidth";
#[derive(Debug, PartialEq)]
pub struct DomainIntervalOperator {
domain: Domain,
scale: ScaleType,
}

impl DomainIntervalOperator {
pub(crate) fn new(domain: Domain) -> Self {
DomainIntervalOperator { domain }
pub(crate) fn new(domain: Domain, scale: ScaleType) -> Self {
DomainIntervalOperator { domain, scale }
}

fn resolve_domain(&self, values: &[DataValue]) -> Option<(f32, f32)> {
Expand Down Expand Up @@ -119,6 +121,12 @@ impl Evaluation for DomainDiscreteOperator {
}
}

#[derive(Debug, PartialEq)]
pub enum ScaleType {
Linear,
Log,
}

/// `LinearOperator` represents an operator of the graph, which linearly scales data values from a
/// certain `field` reference, and creates a new field in the defined `output` field.
#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -188,6 +196,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,15 +399,19 @@ mod tests {

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

use super::{BandOperator, DomainIntervalOperator, LinearOperator};

#[tokio::test]
async fn domain_applies_for_literal() {
let operator = DomainIntervalOperator::new(Domain::Literal(vec![0.0, 5.0]));
let operator = DomainIntervalOperator::new(Domain::Literal(vec![0.0, 5.0]), ScaleType::Linear);
let pulse = operator.evaluate(Pulse::data(vec![])).await;

assert_eq!(pulse, Pulse::domain(ResolvedDomain::Interval(0.0, 5.0)))
Expand All @@ -345,21 +426,27 @@ mod tests {
DataValue::from_pairs(vec![("a", 15.0.into()), ("b", 1.0.into())]),
];

let operator = DomainIntervalOperator::new(Domain::DataField {
data: "primary".to_string(),
field: "a".to_string(),
});
let operator = DomainIntervalOperator::new(
Domain::DataField {
data: "primary".to_string(),
field: "a".to_string(),
},
ScaleType::Linear,
);
let pulse = operator.evaluate(Pulse::data(series)).await;

assert_eq!(pulse, Pulse::domain(ResolvedDomain::Interval(-2.0, 15.0)));
}

#[tokio::test]
async fn domain_handles_empty_data() {
let operator = DomainIntervalOperator::new(Domain::DataField {
data: "primary".to_string(),
field: "a".to_string(),
});
let operator = DomainIntervalOperator::new(
Domain::DataField {
data: "primary".to_string(),
field: "a".to_string(),
},
ScaleType::Linear,
);
let pulse = operator.evaluate(Pulse::data(Vec::new())).await;

assert_eq!(pulse, Pulse::data(Vec::new()));
Expand Down Expand Up @@ -491,4 +578,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, |x| logn(x))
}

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
1 change: 1 addition & 0 deletions bruc-core/src/graph/pulse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl MultiPulse {

#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedDomain {
// TODO: use logarithmic scale here as well so the axis are also in logarithmic scale if defined so.
Interval(f32, f32),
Discrete {
values: Vec<DataItem>,
Expand Down
Loading

0 comments on commit a300b19

Please sign in to comment.