rcharts 0.1.0 → 0.1.1
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 +4 -4
- data/app/helpers/rcharts/graph_helper/graph/axis/caster.rb +19 -4
- data/app/helpers/rcharts/graph_helper/graph/axis/domain.rb +84 -0
- data/app/helpers/rcharts/graph_helper/graph/axis/positioning.rb +68 -15
- data/app/helpers/rcharts/graph_helper/graph/axis/ticks.rb +43 -20
- data/lib/rcharts/version.rb +1 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 15185c259bd39b4d6a998939a98356bf1513693e521ed693cb80907f8aab593e
|
|
4
|
+
data.tar.gz: acdb9c9f5f0d2c8336b22e78f7f49f3868485b112bb652e854f34bfeacdcd3f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a8e3de34d0ec122623157e9f6334c46185403b93949d541e7184841e0ded9e7479dd7b09961c26c51e71c0eceed39e72cd24974252ab42bb937c03c11dac8a7
|
|
7
|
+
data.tar.gz: 4c83d7996aedaab2a5442c1b299ed020d5c8ff2e5ebdf16646e5d0304a12591ff2f9d6c2c245e917cd1dd4141599e1cdbeed567efcb050d65244c65d5c7cbfe2
|
|
@@ -5,6 +5,8 @@ module RCharts
|
|
|
5
5
|
module Graph
|
|
6
6
|
class Axis
|
|
7
7
|
class Caster # :nodoc:
|
|
8
|
+
EPOCH_OFFSET = Time.at(0, in: 'Z').to_date.jd
|
|
9
|
+
|
|
8
10
|
def initialize(value)
|
|
9
11
|
@value = value
|
|
10
12
|
end
|
|
@@ -16,7 +18,7 @@ module RCharts
|
|
|
16
18
|
def downcast
|
|
17
19
|
case value
|
|
18
20
|
when Time then value.to_i
|
|
19
|
-
when Date then
|
|
21
|
+
when Date then julian_days.to_i
|
|
20
22
|
else value
|
|
21
23
|
end
|
|
22
24
|
end
|
|
@@ -28,13 +30,26 @@ module RCharts
|
|
|
28
30
|
def upcast(raw)
|
|
29
31
|
case value
|
|
30
32
|
when ActiveSupport::TimeWithZone then value.time_zone.at(raw)
|
|
31
|
-
when Time then
|
|
32
|
-
when
|
|
33
|
-
when Date then Date.jd(raw, value.start)
|
|
33
|
+
when Time then value.class.at(raw, in: value_zone)
|
|
34
|
+
when Date then upcast_date(raw)
|
|
34
35
|
else raw
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
def upcast_date(raw)
|
|
40
|
+
raw.seconds.in_days.divmod(1).then do |days, fraction|
|
|
41
|
+
value.class.jd(EPOCH_OFFSET + days, *datetime_args, value.start) + fraction
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def datetime_args
|
|
46
|
+
[0, 0, 0, value.offset] if value.is_a?(DateTime)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def julian_days
|
|
50
|
+
((value.jd - EPOCH_OFFSET) + (value.day_fraction - value.try(:offset).to_f)).days
|
|
51
|
+
end
|
|
52
|
+
|
|
38
53
|
def value_zone
|
|
39
54
|
value.zone.try { ActiveSupport::TimeZone[it] } || value.utc_offset
|
|
40
55
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCharts
|
|
4
|
+
module GraphHelper
|
|
5
|
+
module Graph
|
|
6
|
+
class Axis
|
|
7
|
+
class Domain # :nodoc:
|
|
8
|
+
FLOOR_ALIGNMENT = { ...(2.days.to_i) => -> { it.beginning_of_day },
|
|
9
|
+
(2.days.to_i)...(2.weeks.to_i) => -> { it.beginning_of_week },
|
|
10
|
+
(2.weeks.to_i)...(2.months.to_i) => -> { it.beginning_of_month },
|
|
11
|
+
(2.months.to_i)...(4.months.to_i) => -> { it.beginning_of_quarter },
|
|
12
|
+
(4.months.to_i)...(10.months.to_i) => -> { it.beginning_of_year.advance(months: it.month < 7 ? 0 : 6) },
|
|
13
|
+
(10.months.to_i)... => -> { it.beginning_of_year } }.freeze
|
|
14
|
+
MODES = %i[exact rounded].freeze
|
|
15
|
+
|
|
16
|
+
def initialize(minimum:, maximum:, tick_interval:, mode:)
|
|
17
|
+
@minimum = minimum
|
|
18
|
+
@maximum = maximum
|
|
19
|
+
@tick_interval = tick_interval
|
|
20
|
+
@mode = mode.presence_in(MODES) || raise(ArgumentError, "mode must be one of #{MODES.collect(&:inspect).join(', ')}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def domain_minimum
|
|
24
|
+
return minimum if exact?
|
|
25
|
+
|
|
26
|
+
adjusted_minimum
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def domain_maximum
|
|
30
|
+
return maximum if exact?
|
|
31
|
+
|
|
32
|
+
adjusted_maximum
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def exact?
|
|
36
|
+
mode == :exact
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def rounded?
|
|
40
|
+
mode == :rounded
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
attr_reader :minimum, :maximum, :tick_interval, :mode
|
|
46
|
+
|
|
47
|
+
def adjusted_minimum
|
|
48
|
+
@adjusted_minimum ||= calculate_adjusted_minimum
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def calculate_adjusted_minimum
|
|
52
|
+
return minimum if tick_interval.zero?
|
|
53
|
+
return calendar_boundary_for(minimum, tick_interval) if temporal_data?
|
|
54
|
+
|
|
55
|
+
Caster.new(minimum).casting { (it / tick_interval).floor * tick_interval }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def adjusted_maximum
|
|
59
|
+
@adjusted_maximum ||= adjusted_minimum + (tick_count * tick_interval)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def tick_count
|
|
63
|
+
@tick_count ||= tick_interval.zero? ? 0 : (tick_count_range / tick_interval).ceil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def tick_count_range
|
|
67
|
+
temporal_data? ? (maximum.to_time - adjusted_minimum.to_time).abs : (maximum - adjusted_minimum).to_f
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def temporal_data?
|
|
71
|
+
minimum.acts_like?(:time) || minimum.acts_like?(:date)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def calendar_boundary_for(value, interval)
|
|
75
|
+
FLOOR_ALIGNMENT.detect { |range, _| range.cover?(interval) }
|
|
76
|
+
.last
|
|
77
|
+
.call(value.to_time)
|
|
78
|
+
.then { value.acts_like?(:date) ? it.to_date : it }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -5,6 +5,18 @@ module RCharts
|
|
|
5
5
|
module Graph
|
|
6
6
|
class Axis
|
|
7
7
|
module Positioning # :nodoc:
|
|
8
|
+
QUANTIZATION = { ...(5.days) => :adjusted_minimum,
|
|
9
|
+
(5.days)...(2.weeks) => :week_start,
|
|
10
|
+
(2.weeks)...(2.months) => :month_start,
|
|
11
|
+
(2.months)...(4.months) => :quarter_start,
|
|
12
|
+
(4.months)...(10.months) => :half_year_start,
|
|
13
|
+
(10.months)... => :year_start }.freeze
|
|
14
|
+
ROUNDING = { ...(1.day) => -> { it },
|
|
15
|
+
(1.day)...(7.days) => -> { it.in_days.round.days },
|
|
16
|
+
(7.days)...(1.month) => -> { it.in_weeks.round.weeks },
|
|
17
|
+
(1.month)...(1.year) => -> { it.in_months.round.months },
|
|
18
|
+
(1.year)... => -> { it.in_years.round.years } }.freeze
|
|
19
|
+
|
|
8
20
|
extend ActiveSupport::Concern
|
|
9
21
|
include Ticks
|
|
10
22
|
|
|
@@ -12,20 +24,16 @@ module RCharts
|
|
|
12
24
|
def ticks
|
|
13
25
|
return {} unless values.any?
|
|
14
26
|
|
|
15
|
-
@ticks ||= tick_count.succ
|
|
16
|
-
|
|
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)))
|
|
27
|
+
@ticks ||= Array.new(tick_count.succ) { value_at(it) }
|
|
28
|
+
.reject { it.nil? || it > adjusted_maximum }
|
|
29
|
+
.index_by { position_for(it) }
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def value_at(index)
|
|
25
33
|
return if index > tick_count || index.negative? || values.empty?
|
|
26
|
-
return values.dig(index, 0) if discrete
|
|
34
|
+
return values.dig(index, 0) if discrete
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
quantized_adjusted_minimum + (index * rounded_tick_interval)
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
def length_between(min, max)
|
|
@@ -41,22 +49,67 @@ module RCharts
|
|
|
41
49
|
downcasted_position_for(value)
|
|
42
50
|
end
|
|
43
51
|
|
|
52
|
+
def position_at(index)
|
|
53
|
+
return if index > tick_count
|
|
54
|
+
|
|
55
|
+
(tick_offset.to_f + raw_position_at(index))
|
|
56
|
+
end
|
|
57
|
+
|
|
44
58
|
private
|
|
45
59
|
|
|
60
|
+
def raw_position_at(index)
|
|
61
|
+
index * (100.0 / ((categorical? ? values_count.nonzero? : tick_count.nonzero?) || 1))
|
|
62
|
+
end
|
|
63
|
+
|
|
46
64
|
def downcasted_position_for(value)
|
|
47
|
-
((Caster.new(value).downcast.to_f -
|
|
65
|
+
((Caster.new(value).downcast.to_f - Caster.new(adjusted_minimum).downcast) / casted_range) * 100
|
|
48
66
|
end
|
|
49
67
|
|
|
50
68
|
def casted_range
|
|
51
|
-
|
|
69
|
+
Caster.new(adjusted_maximum).downcast - Caster.new(adjusted_minimum).downcast
|
|
52
70
|
end
|
|
53
71
|
|
|
54
|
-
def
|
|
55
|
-
|
|
72
|
+
def rounded_tick_interval
|
|
73
|
+
return tick_interval unless temporal_data?
|
|
74
|
+
|
|
75
|
+
ROUNDING.detect { |range, _value| range.cover?(tick_interval) }
|
|
76
|
+
.last
|
|
77
|
+
.call(tick_interval)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def quantized_adjusted_minimum
|
|
81
|
+
return adjusted_minimum unless temporal_data?
|
|
82
|
+
|
|
83
|
+
QUANTIZATION.detect { |range, _value| range.cover?(tick_interval) }
|
|
84
|
+
.last
|
|
85
|
+
.then { send(it) }
|
|
86
|
+
.then { tick_interval < 1.day ? it.to_time : it }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def week_start
|
|
90
|
+
adjusted_minimum.days_to_week_start.zero? ? adjusted_minimum.beginning_of_week : adjusted_minimum.next_week
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def month_start
|
|
94
|
+
adjusted_minimum.mday == 1 ? adjusted_minimum.beginning_of_month : adjusted_minimum.next_month.beginning_of_month
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def quarter_start
|
|
98
|
+
if adjusted_minimum.yday == adjusted_minimum.beginning_of_quarter.yday
|
|
99
|
+
adjusted_minimum.beginning_of_quarter
|
|
100
|
+
else
|
|
101
|
+
adjusted_minimum.next_quarter.beginning_of_quarter
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def half_year_start
|
|
106
|
+
return adjusted_minimum if adjusted_minimum.month.modulo(6) == 1 && adjusted_minimum.mday == 1
|
|
107
|
+
|
|
108
|
+
adjusted_minimum.beginning_of_year.advance(months: adjusted_minimum.month < 7 ? 6 : 12)
|
|
56
109
|
end
|
|
57
110
|
|
|
58
|
-
def
|
|
59
|
-
|
|
111
|
+
def year_start
|
|
112
|
+
adjusted_minimum.yday == 1 ? adjusted_minimum.beginning_of_year : adjusted_minimum.next_year.beginning_of_year
|
|
60
113
|
end
|
|
61
114
|
end
|
|
62
115
|
end
|
|
@@ -5,27 +5,56 @@ module RCharts
|
|
|
5
5
|
module Graph
|
|
6
6
|
class Axis
|
|
7
7
|
module Ticks # :nodoc:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
DECIMAL_INTERVALS = [1, 2, 2.5, 5, 10].freeze
|
|
9
|
+
TEMPORAL_INTERVALS = [1.second, 2.seconds, 5.seconds, 10.seconds, 15.seconds, 30.seconds,
|
|
10
|
+
1.minute, 2.minutes, 5.minutes, 10.minutes, 15.minutes, 30.minutes,
|
|
11
|
+
1.hour, 2.hours, 3.hours, 6.hours, 12.hours,
|
|
12
|
+
1.day, 2.days,
|
|
13
|
+
1.week, 2.weeks,
|
|
14
|
+
1.month, 2.months, 3.months, 6.months,
|
|
15
|
+
1.year, 2.years, 5.years, 10.years, 20.years, 50.years, 100.years].freeze
|
|
16
|
+
TARGET_TICK_COUNT = 10.0
|
|
11
17
|
|
|
12
18
|
extend ActiveSupport::Concern
|
|
13
19
|
|
|
14
20
|
included do
|
|
15
21
|
def tick_count
|
|
16
|
-
return values_count.pred if
|
|
22
|
+
return values_count.pred if categorical?
|
|
23
|
+
return values_count if discrete?
|
|
17
24
|
|
|
18
|
-
(
|
|
25
|
+
(range / tick_interval.to_f).ceil
|
|
19
26
|
end
|
|
20
27
|
|
|
21
28
|
def adjusted_minimum
|
|
22
|
-
@adjusted_minimum ||=
|
|
29
|
+
@adjusted_minimum ||= domain.domain_minimum
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def adjusted_maximum
|
|
33
|
+
@adjusted_maximum ||= domain.domain_maximum
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def domain
|
|
37
|
+
@domain ||= Domain.new(minimum:, maximum:, tick_interval: tick_interval.to_f,
|
|
38
|
+
mode: ((values_method == :keys && temporal_data?) || discrete? ? :exact : :rounded))
|
|
23
39
|
end
|
|
24
40
|
|
|
25
41
|
private
|
|
26
42
|
|
|
27
43
|
def interval
|
|
28
|
-
|
|
44
|
+
interval_range / TARGET_TICK_COUNT
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def range
|
|
48
|
+
return (maximum.to_time - adjusted_minimum.to_time).abs if temporal_data?
|
|
49
|
+
|
|
50
|
+
maximum - adjusted_minimum
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def interval_range
|
|
54
|
+
return (maximum ? 1 : 0) if categorical?
|
|
55
|
+
return (maximum.to_time - minimum.to_time).abs if temporal_data?
|
|
56
|
+
|
|
57
|
+
(maximum + adjustment_for_empty_interval) - (minimum - adjustment_for_empty_interval)
|
|
29
58
|
end
|
|
30
59
|
|
|
31
60
|
def adjustment_for_empty_interval
|
|
@@ -36,15 +65,13 @@ module RCharts
|
|
|
36
65
|
|
|
37
66
|
def tick_interval
|
|
38
67
|
return 0 if interval <= 0
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
tick_base *
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else 10
|
|
47
|
-
end
|
|
68
|
+
return TEMPORAL_INTERVALS.min_by { (it - interval).abs } if temporal_data?
|
|
69
|
+
|
|
70
|
+
tick_base * DECIMAL_INTERVALS.min_by { |n| n < (interval / tick_base) ? Float::INFINITY : n }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def temporal_data?
|
|
74
|
+
minimum.acts_like?(:time) || minimum.acts_like?(:date)
|
|
48
75
|
end
|
|
49
76
|
|
|
50
77
|
def tick_base
|
|
@@ -54,10 +81,6 @@ module RCharts
|
|
|
54
81
|
def tick_offset
|
|
55
82
|
(100.0 / values_count) / 2 if categorical?
|
|
56
83
|
end
|
|
57
|
-
|
|
58
|
-
def adjusted_maximum
|
|
59
|
-
@adjusted_maximum ||= adjusted_minimum + (tick_count * tick_interval)
|
|
60
|
-
end
|
|
61
84
|
end
|
|
62
85
|
end
|
|
63
86
|
end
|
data/lib/rcharts/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rcharts
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Malčić
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- app/helpers/rcharts/graph_helper/graph/axes.rb
|
|
50
50
|
- app/helpers/rcharts/graph_helper/graph/axis.rb
|
|
51
51
|
- app/helpers/rcharts/graph_helper/graph/axis/caster.rb
|
|
52
|
+
- app/helpers/rcharts/graph_helper/graph/axis/domain.rb
|
|
52
53
|
- app/helpers/rcharts/graph_helper/graph/axis/positioning.rb
|
|
53
54
|
- app/helpers/rcharts/graph_helper/graph/axis/ticks.rb
|
|
54
55
|
- app/helpers/rcharts/graph_helper/graph/calculator.rb
|
|
@@ -101,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
101
102
|
- !ruby/object:Gem::Version
|
|
102
103
|
version: '0'
|
|
103
104
|
requirements: []
|
|
104
|
-
rubygems_version:
|
|
105
|
+
rubygems_version: 4.0.3
|
|
105
106
|
specification_version: 4
|
|
106
107
|
summary: Responsive SVG charting for Action View.
|
|
107
108
|
test_files: []
|