lib/chartnado/series/wrap.rb
module Chartnado
module Series
class Wrap < SimpleDelegator
def self.[](series)
series.class == self ? series : new(series)
end
def *(val)
times(val, precision: nil)
end
def times(factor, precision: 2)
factor = wrap(factor)
return factor.times(self, precision: precision) if factor.dimensions > dimensions
return with_precision(precision, factor.to_f * to_f) unless dimensions > 1
return self unless length > 0
if has_separate_named_series? || array? && first.is_a?(Array)
result = map { |(name, data)| [name, wrap(data) * factor] }
elsif hash?
result = to_a.reduce({}) do |hash, (key, value)|
if factor.hash?
if key.is_a?(Array)
scalar = factor[key.second]
else
scalar = factor[key]
end
else
scalar = factor
end
scalar ||= 0
hash[key] = scalar * value
hash
end
else
result = map do |value|
factor * value
end
end
wrap(result)
end
def add(*series, scalar_sum: 0.0)
(series, scalars) = [__getobj__, *series].partition { |s| s.respond_to?(:map) }
scalar_sum += scalars.reduce(:+) || 0.0
return wrap(scalar_sum) unless series.present?
if wrap(series.first).has_separate_named_series?
result = series.map(&:to_a).flatten(1).group_by(&:first).map do |name, values|
data = values.map(&:second).reduce(Hash.new(scalar_sum)) do |hash, values|
values.each do |key, value|
hash[key] += value
end
hash
end
[
name, data
]
end
elsif series.first.is_a?(Hash)
keys = series.flat_map(&:keys).uniq
result = keys.reduce({}) do |hash, key|
hash[key] = (series.map { |s| s[key] }.compact.reduce(:+) || 0) + scalar_sum
hash
end
elsif series.first.is_a?(Array)
result = series.map { |s| s.reduce(:+) + scalar_sum }
else
result = scalar_sum
end
wrap(result)
end
def over(bottom, multiplier: 1.0, precision: 2)
bottom = wrap(bottom)
return times(1.0 * multiplier / bottom, precision: precision) if bottom.dimensions == 1
if dimensions > bottom.dimensions
top_series_by_name = data_by_name
if has_separate_named_series?
data_by_name.map do |name, top_values|
[
name,
wrap(top_values).
over(bottom, multiplier: multiplier, precision: precision)
]
end
else
bottom.reduce({}) do |hash, (key, value)|
top_series_by_name.keys.each do |name|
top_key = [name, *Array.wrap(key)]
top_value = top_series_by_name[name][top_key]
if top_value
hash[top_key] = wrap(top_value).
over(value, multiplier: multiplier, precision: precision)
end
end
hash
end
end
elsif array_of_named_series?
top_series_by_name = data_by_name
bottom.map do |(name, data)|
[
name,
wrap(top_series_by_name[name]).
over(data, multiplier: multiplier, precision: precision)
]
end
elsif bottom.respond_to?(:reduce)
bottom.reduce({}) do |hash, (key, value)|
hash[key] = wrap(self[key] || 0).
over(value, multiplier: multiplier, precision: precision)
hash
end
else
with_precision(precision, to_f * multiplier.to_f / bottom.to_f)
end
end
def has_multiple_series?
array_of_named_series? || is_a?(Hash) && begin
first_series = series.first
first_series[0].is_a?(Array) && first_series[0].length > 1 || first_series[1].respond_to?(:length)
end
end
def hash?
__getobj__.is_a?(Hash)
end
def array?
__getobj__.is_a?(Array)
end
def array_of_named_series?
array? && first.second.is_a?(Hash)
end
def hash_of_named_series?
hash? && values.first && values.first.is_a?(Hash)
end
def dimensions
return 1 unless respond_to?(:length)
if hash?
if keys.first && keys.first.is_a?(Array) || hash_of_named_series?
3
else
2
end
else
if first && first.is_a?(Array)
3
else
2
end
end
end
def has_separate_named_series?
hash_of_named_series? || array_of_named_series?
end
private
def data_by_name
return self if hash_of_named_series?
result = if array_of_named_series?
reduce({}) do |hash, (name, values)|
hash[name] = values
hash
end
else
hash = Hash.new { |hash, key| hash[key] = {} }
x = reduce(hash) do |hash, (key, value)|
p key.first
new_key = Array.wrap(key.first).first
hash[new_key][key] = value
hash
end
p x
x
end
wrap(result)
end
def with_precision(precision, value)
value = value.round(precision) if precision
value
end
def wrap(val)
self.class[val]
end
end
end
end