rcharts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +99 -0
- data/Rakefile +21 -0
- data/app/helpers/rcharts/graph_helper/axes/axis_element/styles.rb +91 -0
- data/app/helpers/rcharts/graph_helper/axes/axis_element.rb +89 -0
- data/app/helpers/rcharts/graph_helper/axes/label_element.rb +32 -0
- data/app/helpers/rcharts/graph_helper/axes/tick_element.rb +38 -0
- data/app/helpers/rcharts/graph_helper/axes.rb +8 -0
- data/app/helpers/rcharts/graph_helper/categories/bar_builder.rb +32 -0
- data/app/helpers/rcharts/graph_helper/categories/bar_segment_element.rb +49 -0
- data/app/helpers/rcharts/graph_helper/categories/bars_element.rb +52 -0
- data/app/helpers/rcharts/graph_helper/categories/category_builder.rb +115 -0
- data/app/helpers/rcharts/graph_helper/element.rb +65 -0
- data/app/helpers/rcharts/graph_helper/element_builder.rb +42 -0
- data/app/helpers/rcharts/graph_helper/graph/axes.rb +41 -0
- data/app/helpers/rcharts/graph_helper/graph/axis/caster.rb +45 -0
- data/app/helpers/rcharts/graph_helper/graph/axis/positioning.rb +66 -0
- data/app/helpers/rcharts/graph_helper/graph/axis/ticks.rb +66 -0
- data/app/helpers/rcharts/graph_helper/graph/axis.rb +86 -0
- data/app/helpers/rcharts/graph_helper/graph/calculator.rb +91 -0
- data/app/helpers/rcharts/graph_helper/graph/composition.rb +35 -0
- data/app/helpers/rcharts/graph_helper/graph/options.rb +33 -0
- data/app/helpers/rcharts/graph_helper/graph.rb +8 -0
- data/app/helpers/rcharts/graph_helper/graph_builder.rb +270 -0
- data/app/helpers/rcharts/graph_helper/legend_entry_builder.rb +46 -0
- data/app/helpers/rcharts/graph_helper/rule_element.rb +68 -0
- data/app/helpers/rcharts/graph_helper/series/area_element.rb +50 -0
- data/app/helpers/rcharts/graph_helper/series/path.rb +153 -0
- data/app/helpers/rcharts/graph_helper/series/path_element.rb +72 -0
- data/app/helpers/rcharts/graph_helper/series/point.rb +58 -0
- data/app/helpers/rcharts/graph_helper/series/scatter_element.rb +87 -0
- data/app/helpers/rcharts/graph_helper/series/series_builder.rb +145 -0
- data/app/helpers/rcharts/graph_helper/tooltips/entry_builder.rb +47 -0
- data/app/helpers/rcharts/graph_helper/tooltips/foreign_object_element.rb +84 -0
- data/app/helpers/rcharts/graph_helper/tooltips/hover_target_element.rb +39 -0
- data/app/helpers/rcharts/graph_helper/tooltips/marker_element.rb +38 -0
- data/app/helpers/rcharts/graph_helper/tooltips/tooltip_builder.rb +44 -0
- data/app/helpers/rcharts/graph_helper/tooltips/tooltip_element.rb +45 -0
- data/app/helpers/rcharts/graph_helper.rb +249 -0
- data/lib/generators/rcharts/install/install_generator.rb +13 -0
- data/lib/generators/rcharts/install/templates/rcharts.css +392 -0
- data/lib/rcharts/engine.rb +25 -0
- data/lib/rcharts/percentage.rb +36 -0
- data/lib/rcharts/type/percentage.rb +20 -0
- data/lib/rcharts/type/symbol.rb +29 -0
- data/lib/rcharts/type.rb +9 -0
- data/lib/rcharts/version.rb +6 -0
- data/lib/rcharts.rb +52 -0
- data/lib/tasks/rcharts_tasks.rake +6 -0
- metadata +107 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
# = \Element
|
|
6
|
+
#
|
|
7
|
+
# The base class for all elements to inherit from. Elements are designed for cases where a tag helper alone
|
|
8
|
+
# would be unwieldy because of the complexity of determining content or attribute values e.g. for SVG positioning
|
|
9
|
+
# attributes like <tt>x</tt> and <tt>y</tt>.
|
|
10
|
+
#
|
|
11
|
+
# To ensure rendering yields a predictable result every time, don't mutate element state as part of the rendering
|
|
12
|
+
# process. If you need to track and mutate state, use ElementBuilder instead, which is designed for this situation.
|
|
13
|
+
#
|
|
14
|
+
# By default, rendering an element produces no content. Subclasses should implement their own private #tags method
|
|
15
|
+
# to return actual content when rendered. All helpers and objects accessible in the parent view context are also
|
|
16
|
+
# accessible within #tags, so you can use tag helpers and other helpers to build markup.
|
|
17
|
+
#
|
|
18
|
+
# To pass a value to an element, first declare an attribute using the ActiveModel::Attributes API
|
|
19
|
+
# and then pass the value to the element's initializer (or use one of the other methods to set attributes).
|
|
20
|
+
# Already declared attributes are #id, #class_names (with writer alias #class=), #data, and #aria.
|
|
21
|
+
class Element
|
|
22
|
+
include ActiveModel::API
|
|
23
|
+
include ActiveModel::Attributes
|
|
24
|
+
|
|
25
|
+
class_attribute :view_context
|
|
26
|
+
|
|
27
|
+
delegate :render, :tag, to: :view_context
|
|
28
|
+
delegate_missing_to :view_context
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# :attr_accessor: id
|
|
32
|
+
attribute :id, :string
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# :attr_accessor:
|
|
36
|
+
attribute :class_names
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# :attr_writer: class
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# :attr_accessor:
|
|
43
|
+
attribute :data, default: -> { {} }
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :attr_accessor:
|
|
47
|
+
attribute :aria, default: -> { {} }
|
|
48
|
+
|
|
49
|
+
alias class= class_names= # :nodoc:
|
|
50
|
+
|
|
51
|
+
def render_in(view_context, &)
|
|
52
|
+
with view_context: do
|
|
53
|
+
tags(&)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# :doc:
|
|
60
|
+
def tags
|
|
61
|
+
''.html_safe
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
# = \Element Builder
|
|
6
|
+
#
|
|
7
|
+
# Like form builders, but for elements. Concrete builders should inherit from this class.
|
|
8
|
+
# Use this when you need to provide a way for users to choose between different options at rendering time and track
|
|
9
|
+
# rendering state (e.g. to do with iteration).
|
|
10
|
+
#
|
|
11
|
+
# Rendering a builder shouldn't itself render anything. Instead, rendering the builder with a block means that,
|
|
12
|
+
# just as with a form builder, the block will be called with an instance of the builder.
|
|
13
|
+
# That means within the block, users can then call methods on the builder, which may output markup to the buffer.
|
|
14
|
+
# If you need a container element, like the <tt><form></tt> associated with a form builder,
|
|
15
|
+
# keep that separate from the builder.
|
|
16
|
+
#
|
|
17
|
+
# The builder is the place to store information about the state of rendering independent of particular elements.
|
|
18
|
+
# One example is iteration, where users may need to be able to specify different markup for each individual item
|
|
19
|
+
# in the collection. You can allow this by rendering a builder with each object in the collection, declaring
|
|
20
|
+
# an index attribute on the builder to allow different behavior according to the position of the item:
|
|
21
|
+
# MyElementBuilder < RCharts::GraphHelper::ElementBuilder
|
|
22
|
+
# attribute :index, :integer
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# Like elements, builders set and delegate to the view context.
|
|
26
|
+
class ElementBuilder
|
|
27
|
+
include ActiveModel::API
|
|
28
|
+
include ActiveModel::Attributes
|
|
29
|
+
|
|
30
|
+
class_attribute :view_context
|
|
31
|
+
|
|
32
|
+
delegate :render, :tag, to: :view_context
|
|
33
|
+
delegate_missing_to :view_context
|
|
34
|
+
|
|
35
|
+
def render_in(view_context, &)
|
|
36
|
+
with view_context: do
|
|
37
|
+
block_given? ? yield(self) : ''.html_safe
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Axes # :nodoc:
|
|
7
|
+
delegate :[], to: :axes
|
|
8
|
+
|
|
9
|
+
def initialize(graphable = {}, axis_options = {})
|
|
10
|
+
@axes = Options.new(graphable.keys, axis_options).to_h do |name, axes|
|
|
11
|
+
[name, axes.collect { |index, options| build_axis(graphable:, name:, index:, **options) }]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fetch(name, index = 0)
|
|
16
|
+
axes.fetch(name) { raise ArgumentError, "Unknown axis #{name}" }
|
|
17
|
+
.fetch(index) { raise ArgumentError, "Unknown index #{index} for axis #{name}" }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def discrete
|
|
21
|
+
fetch(:x).then { it.discrete? && it } || axes.values.flatten.find(&:discrete?) || fetch(:x)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def continuous
|
|
25
|
+
fetch(:y).then { !it.discrete? && it } || axes.values.flatten.find { !it.discrete? } || fetch(:y)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :axes
|
|
31
|
+
|
|
32
|
+
def build_axis(graphable:, name:, index:, **options)
|
|
33
|
+
discrete = case options[:values_method].to_proc.call(graphable).first
|
|
34
|
+
when String, Symbol then :categorical
|
|
35
|
+
end
|
|
36
|
+
Axis.new(graphable:, name:, index:, discrete:, **options)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Axis
|
|
7
|
+
class Caster # :nodoc:
|
|
8
|
+
def initialize(value)
|
|
9
|
+
@value = value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def casting
|
|
13
|
+
upcast(yield downcast)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def downcast
|
|
17
|
+
case value
|
|
18
|
+
when Time then value.to_i
|
|
19
|
+
when Date then value.jd + value.day_fraction
|
|
20
|
+
else value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :value
|
|
27
|
+
|
|
28
|
+
def upcast(raw)
|
|
29
|
+
case value
|
|
30
|
+
when ActiveSupport::TimeWithZone then value.time_zone.at(raw)
|
|
31
|
+
when Time then Time.at(raw, in: value_zone)
|
|
32
|
+
when DateTime then DateTime.jd(0, 0, 0, 0, value.offset, value.start) + raw
|
|
33
|
+
when Date then Date.jd(raw, value.start)
|
|
34
|
+
else raw
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def value_zone
|
|
39
|
+
value.zone.try { ActiveSupport::TimeZone[it] } || value.utc_offset
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Axis
|
|
7
|
+
module Positioning # :nodoc:
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
include Ticks
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
def ticks
|
|
13
|
+
return {} unless values.any?
|
|
14
|
+
|
|
15
|
+
@ticks ||= tick_count.succ.times.to_h { [position_at(it), value_at(it)] }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def position_at(index)
|
|
19
|
+
return if index > tick_count
|
|
20
|
+
|
|
21
|
+
((index * (100.0 / (tick_count.nonzero? || 1))) + tick_offset.to_f) * (100 / (100 + (2 * tick_offset.to_f)))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def value_at(index)
|
|
25
|
+
return if index > tick_count || index.negative? || values.empty?
|
|
26
|
+
return values.dig(index, 0) if discrete || values.dig(0, 0).is_a?(String)
|
|
27
|
+
|
|
28
|
+
adjusted_minimum + (index * tick_interval)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def length_between(min, max)
|
|
32
|
+
return position_for(0 - max) - position_for(0 - min) if max.negative?
|
|
33
|
+
|
|
34
|
+
(position_for(max) - position_for(min))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def position_for(value)
|
|
38
|
+
return values.flatten.index(value).try { position_at(it) } if discrete == :categorical
|
|
39
|
+
return 0 if adjusted_maximum == adjusted_minimum
|
|
40
|
+
|
|
41
|
+
downcasted_position_for(value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def downcasted_position_for(value)
|
|
47
|
+
((Caster.new(value).downcast.to_f - casted_adjusted_minimum) / casted_range) * 100
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def casted_range
|
|
51
|
+
casted_adjusted_maximum - casted_adjusted_minimum
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def casted_adjusted_maximum
|
|
55
|
+
Caster.new(adjusted_maximum).downcast
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def casted_adjusted_minimum
|
|
59
|
+
Caster.new(adjusted_minimum).downcast
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Axis
|
|
7
|
+
module Ticks # :nodoc:
|
|
8
|
+
INTERVAL_FACTORS = [1, 2, 5, 10, 30].freeze
|
|
9
|
+
INTERVAL_BASES = ActiveSupport::Duration::PARTS_IN_SECONDS
|
|
10
|
+
ALL_INTERVALS = INTERVAL_BASES.values.flat_map { |base| INTERVAL_FACTORS.collect { base * it } }
|
|
11
|
+
|
|
12
|
+
extend ActiveSupport::Concern
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
def tick_count
|
|
16
|
+
return values_count.pred if discrete?
|
|
17
|
+
|
|
18
|
+
((maximum - adjusted_minimum) / tick_interval).ceil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def adjusted_minimum
|
|
22
|
+
@adjusted_minimum ||= Caster.new(minimum).casting { (it / tick_interval).floor * tick_interval }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def interval
|
|
28
|
+
((maximum + adjustment_for_empty_interval) - (minimum - adjustment_for_empty_interval)) / 10.0
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def adjustment_for_empty_interval
|
|
32
|
+
return 0 if minimum != maximum
|
|
33
|
+
|
|
34
|
+
(maximum.nil? || maximum.zero? ? 1.0 : (maximum.abs * 0.1)) * 0.5
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def tick_interval
|
|
38
|
+
return 0 if interval <= 0
|
|
39
|
+
return ALL_INTERVALS.min_by { (it - interval).abs } if minimum.is_a?(Time)
|
|
40
|
+
|
|
41
|
+
tick_base * case (interval / tick_base)
|
|
42
|
+
when ..1 then 1
|
|
43
|
+
when 1..2 then 2
|
|
44
|
+
when 2..2.5 then 2.5
|
|
45
|
+
when 2.5..5 then 5
|
|
46
|
+
else 10
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def tick_base
|
|
51
|
+
10**BigDecimal(Math.log10(interval).floor)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tick_offset
|
|
55
|
+
(100.0 / values_count) / 2 if categorical?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def adjusted_maximum
|
|
59
|
+
@adjusted_maximum ||= adjusted_minimum + (tick_count * tick_interval)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Axis # :nodoc:
|
|
7
|
+
include ActiveModel::API
|
|
8
|
+
include ActiveModel::Attributes
|
|
9
|
+
|
|
10
|
+
include Ticks
|
|
11
|
+
include Positioning
|
|
12
|
+
|
|
13
|
+
attribute :name, :'rcharts/symbol'
|
|
14
|
+
attribute :index, :integer
|
|
15
|
+
attribute :graphable
|
|
16
|
+
attribute :values_method, default: :values
|
|
17
|
+
attribute :minimum
|
|
18
|
+
attribute :maximum
|
|
19
|
+
attribute :discrete
|
|
20
|
+
attribute :stacked, :boolean, default: false
|
|
21
|
+
|
|
22
|
+
alias stacked? stacked
|
|
23
|
+
|
|
24
|
+
def discrete?
|
|
25
|
+
discrete.present?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def categorical?
|
|
29
|
+
discrete == :categorical
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def horizontal?
|
|
33
|
+
name == :x
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def vertical?
|
|
37
|
+
name == :y
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
delegate :count, to: :values, prefix: true, private: true
|
|
43
|
+
|
|
44
|
+
def minimum
|
|
45
|
+
super || (self.minimum = minima.min) || 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def maximum
|
|
49
|
+
super || (self.maximum = maxima.max) || 0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def minmax
|
|
53
|
+
[minimum, maximum]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def keys
|
|
57
|
+
graphable.keys
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def values
|
|
61
|
+
@values ||= graphable.then(&values_method).collect { it.is_a?(Hash) ? it.values : Array.wrap(it) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def minima
|
|
65
|
+
values.filter_map do |value|
|
|
66
|
+
value.reject { it.try(:positive?) }
|
|
67
|
+
.compact
|
|
68
|
+
.presence
|
|
69
|
+
.try(&(stacked? ? :sum : :min))
|
|
70
|
+
.then { it || value.compact.min }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def maxima
|
|
75
|
+
values.filter_map do |value|
|
|
76
|
+
value.reject { it.try(:negative?) }
|
|
77
|
+
.compact
|
|
78
|
+
.presence
|
|
79
|
+
.try(&(stacked? ? :sum : :max))
|
|
80
|
+
.then { it || value.compact.max }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Calculator # :nodoc:
|
|
7
|
+
delegate :[], :dig, to: :to_h
|
|
8
|
+
|
|
9
|
+
def initialize(values = {})
|
|
10
|
+
@values = values
|
|
11
|
+
@chain = []
|
|
12
|
+
@selections = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def sum_complete
|
|
16
|
+
values.transform_values do |value|
|
|
17
|
+
case value
|
|
18
|
+
when Hash then value.values.any?(nil) ? nil : value.values.sum
|
|
19
|
+
when Array then value.any?(nil) ? nil : value.sum
|
|
20
|
+
else value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def signed(sign = nil)
|
|
26
|
+
return self unless sign
|
|
27
|
+
|
|
28
|
+
apply sign do
|
|
29
|
+
values_for_predicate sign == :positive ? :positive? : :negative?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def stacked(exclude_current: false)
|
|
34
|
+
apply :stacked, exclude_current do
|
|
35
|
+
collect_sum inclusive: !exclude_current
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_h
|
|
40
|
+
keys.index_with do |key|
|
|
41
|
+
next values if key.nil?
|
|
42
|
+
|
|
43
|
+
values.transform_values { it[key] }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
attr_accessor :values, :chain
|
|
50
|
+
|
|
51
|
+
def apply!(*operation)
|
|
52
|
+
selections[[*chain, *operation]] ||= yield if block_given?
|
|
53
|
+
self.chain = chain.dup
|
|
54
|
+
chain.append(*operation)
|
|
55
|
+
self.values = selections[chain]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
attr_reader :selections
|
|
61
|
+
|
|
62
|
+
def apply(*operation, &)
|
|
63
|
+
selections[[*chain, *operation]] ||= yield if block_given?
|
|
64
|
+
dup.tap { it.apply!(*operation) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def keys
|
|
68
|
+
case values.values.first
|
|
69
|
+
in Hash => value then value.keys
|
|
70
|
+
in Array => value then (0...value.size).to_a
|
|
71
|
+
else [nil]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def values_for_predicate(predicate)
|
|
76
|
+
values.deep_transform_values { it.try(predicate) == false ? nil : it }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def collect_sum(inclusive: true)
|
|
80
|
+
values.transform_values do |value|
|
|
81
|
+
value.keys.index_with do |key|
|
|
82
|
+
value.keys.index(key).then do |limit|
|
|
83
|
+
value.values.slice(inclusive ? 0..limit : 0...limit).compact.presence&.sum
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Composition # :nodoc:
|
|
7
|
+
attr_reader :axes
|
|
8
|
+
|
|
9
|
+
delegate :sum_complete, :signed, :stacked, to: :calculator
|
|
10
|
+
delegate :keys, to: :data
|
|
11
|
+
|
|
12
|
+
def initialize(graphable = {}, axis_options = {})
|
|
13
|
+
@data = graphable
|
|
14
|
+
@axes = Axes.new(graphable, axis_options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def values
|
|
18
|
+
@values ||= case data.values.first
|
|
19
|
+
in Hash then data
|
|
20
|
+
in Array then data.transform_values { it.index_with.with_index { |_, index| index } }
|
|
21
|
+
else data.transform_values { { nil => it } }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :data
|
|
28
|
+
|
|
29
|
+
def calculator
|
|
30
|
+
@calculator ||= Calculator.new(data)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Options # :nodoc:
|
|
7
|
+
DEFAULTS = { x: { 0 => { values_method: :keys } }, y: { 0 => { values_method: :values } } }.freeze
|
|
8
|
+
|
|
9
|
+
def initialize(keys, options)
|
|
10
|
+
@keys = keys
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h(&)
|
|
15
|
+
DEFAULTS.deep_merge(normalized_options).to_h(&)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :keys, :options
|
|
21
|
+
|
|
22
|
+
def normalized_options
|
|
23
|
+
options.transform_values do |value|
|
|
24
|
+
next value.each_with_index.to_h { |item, index| [index, item] } if value.is_a?(Array)
|
|
25
|
+
next value if value.is_a?(Hash) && value.keys.first.is_a?(Integer)
|
|
26
|
+
|
|
27
|
+
{ 0 => value }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|