rails-data-explorer 0.0.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.
- data/.gitignore +10 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +52 -0
- data/Rakefile +18 -0
- data/lib/rails-data-explorer.rb +44 -0
- data/lib/rails-data-explorer/action_view_extension.rb +12 -0
- data/lib/rails-data-explorer/active_record_extension.rb +14 -0
- data/lib/rails-data-explorer/chart.rb +52 -0
- data/lib/rails-data-explorer/chart/box_plot.rb +79 -0
- data/lib/rails-data-explorer/chart/box_plot_group.rb +109 -0
- data/lib/rails-data-explorer/chart/contingency_table.rb +189 -0
- data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +22 -0
- data/lib/rails-data-explorer/chart/descriptive_statistics_table_group.rb +0 -0
- data/lib/rails-data-explorer/chart/histogram_categorical.rb +73 -0
- data/lib/rails-data-explorer/chart/histogram_quantitative.rb +73 -0
- data/lib/rails-data-explorer/chart/histogram_temporal.rb +78 -0
- data/lib/rails-data-explorer/chart/multi_dimensional_charts.rb +1 -0
- data/lib/rails-data-explorer/chart/parallel_coordinates.rb +89 -0
- data/lib/rails-data-explorer/chart/parallel_set.rb +65 -0
- data/lib/rails-data-explorer/chart/pie_chart.rb +67 -0
- data/lib/rails-data-explorer/chart/scatterplot.rb +120 -0
- data/lib/rails-data-explorer/chart/scatterplot_matrix.rb +1 -0
- data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +120 -0
- data/lib/rails-data-explorer/data_series.rb +115 -0
- data/lib/rails-data-explorer/data_set.rb +127 -0
- data/lib/rails-data-explorer/data_type.rb +34 -0
- data/lib/rails-data-explorer/data_type/categorical.rb +117 -0
- data/lib/rails-data-explorer/data_type/geo.rb +1 -0
- data/lib/rails-data-explorer/data_type/quantitative.rb +109 -0
- data/lib/rails-data-explorer/data_type/quantitative/decimal.rb +13 -0
- data/lib/rails-data-explorer/data_type/quantitative/integer.rb +13 -0
- data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +62 -0
- data/lib/rails-data-explorer/engine.rb +24 -0
- data/lib/rails-data-explorer/exploration.rb +89 -0
- data/lib/rails-data-explorer/statistics/pearsons_chi_squared_independence_test.rb +75 -0
- data/lib/rails-data-explorer/statistics/rng_category.rb +37 -0
- data/lib/rails-data-explorer/statistics/rng_gaussian.rb +24 -0
- data/lib/rails-data-explorer/statistics/rng_power_law.rb +21 -0
- data/lib/rails-data-explorer/utils/color_scale.rb +33 -0
- data/lib/rails-data-explorer/utils/data_binner.rb +8 -0
- data/lib/rails-data-explorer/utils/data_encoder.rb +2 -0
- data/lib/rails-data-explorer/utils/data_quantizer.rb +2 -0
- data/lib/rails-data-explorer/utils/value_formatter.rb +41 -0
- data/rails-data-explorer.gemspec +30 -0
- data/vendor/assets/javascripts/d3.boxplot.js +302 -0
- data/vendor/assets/javascripts/d3.parcoords.js +585 -0
- data/vendor/assets/javascripts/d3.parsets.js +663 -0
- data/vendor/assets/javascripts/d3.v3.js +9294 -0
- data/vendor/assets/javascripts/nv.d3.js +14369 -0
- data/vendor/assets/javascripts/rails-data-explorer.js +19 -0
- data/vendor/assets/stylesheets/bootstrap-theme.css +346 -0
- data/vendor/assets/stylesheets/bootstrap.css +1727 -0
- data/vendor/assets/stylesheets/d3.boxplot.css +20 -0
- data/vendor/assets/stylesheets/d3.parcoords.css +34 -0
- data/vendor/assets/stylesheets/d3.parsets.css +34 -0
- data/vendor/assets/stylesheets/nv.d3.css +769 -0
- data/vendor/assets/stylesheets/rails-data-explorer.css +21 -0
- data/vendor/assets/stylesheets/rde-default-style.css +42 -0
- metadata +250 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
class RailsDataExplorer
|
2
|
+
class Chart
|
3
|
+
class Scatterplot < Chart
|
4
|
+
|
5
|
+
def initialize(_data_set, options = {})
|
6
|
+
@data_set = _data_set
|
7
|
+
@options = {}.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def compute_chart_attrs
|
11
|
+
x_candidates = @data_set.data_series.find_all { |ds|
|
12
|
+
(ds.chart_roles[Chart::Scatterplot] & [:x, :any]).any?
|
13
|
+
}
|
14
|
+
y_candidates = @data_set.data_series.find_all { |ds|
|
15
|
+
(ds.chart_roles[Chart::Scatterplot] & [:y, :any]).any?
|
16
|
+
}
|
17
|
+
color_candidates = @data_set.data_series.find_all { |ds|
|
18
|
+
(ds.chart_roles[Chart::Scatterplot] & [:color, :any]).any?
|
19
|
+
}
|
20
|
+
size_candidates = @data_set.data_series.find_all { |ds|
|
21
|
+
(ds.chart_roles[Chart::Scatterplot] & [:size, :any]).any?
|
22
|
+
}
|
23
|
+
|
24
|
+
x_ds = x_candidates.first
|
25
|
+
y_ds = (y_candidates - [x_ds]).first
|
26
|
+
color_ds = (color_candidates - [x_ds, y_ds]).first
|
27
|
+
size_ds = (size_candidates - [x_ds, y_ds, color_ds]).first
|
28
|
+
|
29
|
+
ca = case @data_set.dimensions_count
|
30
|
+
when 0,1
|
31
|
+
raise(ArgumentError.new("At least two data series required for scatterplot, only #{ @data_set.dimensions_count } given"))
|
32
|
+
when 2
|
33
|
+
key = ''
|
34
|
+
values_hash = x_ds.values.length.times.map { |idx|
|
35
|
+
r = { x: x_ds.values[idx], y: y_ds.values[idx] }
|
36
|
+
r[:color] = color_ds.values[idx] if color_ds
|
37
|
+
r
|
38
|
+
}
|
39
|
+
{
|
40
|
+
values: [ { key: key, values: values_hash } ],
|
41
|
+
x_axis_label: x_ds.name,
|
42
|
+
x_axis_tick_format: x_ds.axis_tick_format,
|
43
|
+
y_axis_label: y_ds.name,
|
44
|
+
y_axis_tick_format: y_ds.axis_tick_format,
|
45
|
+
}
|
46
|
+
when 3
|
47
|
+
visual_attr_ds = color_ds || size_ds
|
48
|
+
raise "No visual_attr_ds given" if visual_attr_ds.nil?
|
49
|
+
data_series_hash = visual_attr_ds.values.uniq.inject({}) { |m,visual_attr|
|
50
|
+
m[visual_attr] = []
|
51
|
+
m
|
52
|
+
}
|
53
|
+
x_ds.values.length.times.each { |idx|
|
54
|
+
data_series_hash[visual_attr_ds.values[idx]] << { x: x_ds.values[idx], y: y_ds.values[idx] }
|
55
|
+
}
|
56
|
+
{
|
57
|
+
values: data_series_hash.map { |k,v| { key: k, values: v } },
|
58
|
+
x_axis_label: x_ds.name,
|
59
|
+
x_axis_tick_format: x_ds.axis_tick_format,
|
60
|
+
y_axis_label: y_ds.name,
|
61
|
+
y_axis_tick_format: y_ds.axis_tick_format,
|
62
|
+
}
|
63
|
+
else
|
64
|
+
end
|
65
|
+
ca
|
66
|
+
end
|
67
|
+
|
68
|
+
def render
|
69
|
+
return '' unless render?
|
70
|
+
chart_attrs = compute_chart_attrs
|
71
|
+
%(
|
72
|
+
<div class="rde-chart rde-scatterplot">
|
73
|
+
<h3 class="rde-chart-title">Scatterplot</h3>
|
74
|
+
<div id="#{ dom_id }", style="height: 400px;">
|
75
|
+
<svg></svg>
|
76
|
+
</div>
|
77
|
+
<script type="text/javascript">
|
78
|
+
(function() {
|
79
|
+
var data = #{ chart_attrs[:values].to_json };
|
80
|
+
|
81
|
+
nv.addGraph(function() {
|
82
|
+
var chart = nv.models.scatterChart()
|
83
|
+
.showDistX(true)
|
84
|
+
.showDistY(true)
|
85
|
+
.useVoronoi(true)
|
86
|
+
.color(d3.scale.category10().range())
|
87
|
+
.transitionDuration(300)
|
88
|
+
;
|
89
|
+
|
90
|
+
chart.xAxis.tickFormat(#{ chart_attrs[:x_axis_tick_format] })
|
91
|
+
.axisLabel('#{ chart_attrs[:x_axis_label] }')
|
92
|
+
;
|
93
|
+
|
94
|
+
chart.yAxis.tickFormat(#{ chart_attrs[:y_axis_tick_format] })
|
95
|
+
.axisLabel('#{ chart_attrs[:y_axis_label] }')
|
96
|
+
;
|
97
|
+
|
98
|
+
chart.tooltipContent(function(key) {
|
99
|
+
return key;
|
100
|
+
});
|
101
|
+
|
102
|
+
d3.select('##{ dom_id } svg')
|
103
|
+
.datum(data)
|
104
|
+
.call(chart);
|
105
|
+
|
106
|
+
nv.utils.windowResize(chart.update);
|
107
|
+
|
108
|
+
chart.dispatch.on('stateChange', function(e) { ('New State:', JSON.stringify(e)); });
|
109
|
+
|
110
|
+
return chart;
|
111
|
+
});
|
112
|
+
})();
|
113
|
+
</script>
|
114
|
+
</div>
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# http://benjiec.github.io/scatter-matrix/demo/demo.html#
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class RailsDataExplorer
|
2
|
+
class Chart
|
3
|
+
class StackedBarChartCategoricalPercent < Chart
|
4
|
+
|
5
|
+
def initialize(_data_set, options = {})
|
6
|
+
@data_set = _data_set
|
7
|
+
@options = {}.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def compute_chart_attrs
|
11
|
+
x_candidates = @data_set.data_series.find_all { |ds|
|
12
|
+
(ds.chart_roles[Chart::ContingencyTable] & [:x, :any]).any?
|
13
|
+
}.sort { |a,b| b.uniq_vals.length <=> a.uniq_vals.length }
|
14
|
+
y_candidates = @data_set.data_series.find_all { |ds|
|
15
|
+
(ds.chart_roles[Chart::ContingencyTable] & [:y, :any]).any?
|
16
|
+
}
|
17
|
+
|
18
|
+
x_ds = x_candidates.first
|
19
|
+
y_ds = (y_candidates - [x_ds]).first
|
20
|
+
|
21
|
+
# initialize data_matrix
|
22
|
+
data_matrix = { :_sum => { :_sum => 0 } }
|
23
|
+
x_ds.uniq_vals.each { |x_val|
|
24
|
+
data_matrix[x_val] = {}
|
25
|
+
data_matrix[x_val][:_sum] = 0
|
26
|
+
y_ds.uniq_vals.each { |y_val|
|
27
|
+
data_matrix[x_val][y_val] = 0
|
28
|
+
data_matrix[:_sum][y_val] = 0
|
29
|
+
}
|
30
|
+
}
|
31
|
+
# populate data_matrix
|
32
|
+
x_ds.values.length.times { |idx|
|
33
|
+
x_val = x_ds.values[idx]
|
34
|
+
y_val = y_ds.values[idx]
|
35
|
+
data_matrix[x_val][y_val] += 1
|
36
|
+
data_matrix[:_sum][y_val] += 1
|
37
|
+
data_matrix[x_val][:_sum] += 1
|
38
|
+
data_matrix[:_sum][:_sum] += 1
|
39
|
+
}
|
40
|
+
|
41
|
+
x_sorted_keys = x_ds.uniq_vals.sort { |a,b|
|
42
|
+
data_matrix[b][:_sum] <=> data_matrix[a][:_sum]
|
43
|
+
}
|
44
|
+
y_sorted_keys = y_ds.uniq_vals.sort { |a,b|
|
45
|
+
data_matrix[:_sum][b] <=> data_matrix[:_sum][a]
|
46
|
+
}
|
47
|
+
|
48
|
+
values = case @data_set.dimensions_count
|
49
|
+
when 2
|
50
|
+
y_sorted_keys.map { |y_val|
|
51
|
+
{
|
52
|
+
key: y_val,
|
53
|
+
values: x_sorted_keys.map { |x_val|
|
54
|
+
{
|
55
|
+
x: x_val,
|
56
|
+
y: (data_matrix[x_val][y_val] / data_matrix[x_val][:_sum].to_f) }
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
else
|
61
|
+
raise(ArgumentError.new("Exactly two data series required for contingency table."))
|
62
|
+
end
|
63
|
+
{
|
64
|
+
values: values,
|
65
|
+
x_axis_label: x_ds.name,
|
66
|
+
x_axis_tick_format: 'function(d) { return d }',
|
67
|
+
y_axis_label: "#{ y_ds.name } distribution [%]",
|
68
|
+
y_axis_tick_format: "d3.format('.1%')",
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def render
|
73
|
+
return '' unless render?
|
74
|
+
ca = compute_chart_attrs
|
75
|
+
%(
|
76
|
+
<div class="rde-chart rde-bar-chart">
|
77
|
+
<h3 class="rde-chart-title">Stacked Bar Chart</h3>
|
78
|
+
<div id="#{ dom_id }", style="height: 200px;">
|
79
|
+
<svg></svg>
|
80
|
+
</div>
|
81
|
+
<script type="text/javascript">
|
82
|
+
(function() {
|
83
|
+
var data = #{ ca[:values].to_json };
|
84
|
+
|
85
|
+
nv.addGraph(function() {
|
86
|
+
var chart = nv.models.multiBarChart()
|
87
|
+
;
|
88
|
+
|
89
|
+
chart.xAxis
|
90
|
+
.axisLabel('#{ ca[:x_axis_label] }')
|
91
|
+
.tickFormat(#{ ca[:x_axis_tick_format] })
|
92
|
+
;
|
93
|
+
|
94
|
+
chart.yAxis
|
95
|
+
.axisLabel('#{ ca[:y_axis_label] }')
|
96
|
+
.tickFormat(#{ ca[:y_axis_tick_format] })
|
97
|
+
;
|
98
|
+
|
99
|
+
chart.multibar.stacked(true);
|
100
|
+
chart.showControls(false);
|
101
|
+
|
102
|
+
d3.select('##{ dom_id } svg')
|
103
|
+
.datum(data)
|
104
|
+
.transition().duration(100)
|
105
|
+
.call(chart)
|
106
|
+
;
|
107
|
+
|
108
|
+
nv.utils.windowResize(chart.update);
|
109
|
+
|
110
|
+
return chart;
|
111
|
+
});
|
112
|
+
})();
|
113
|
+
</script>
|
114
|
+
</div>
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class RailsDataExplorer
|
2
|
+
class DataSeries
|
3
|
+
|
4
|
+
# TODO: Add concept of significant figures for rounding values when displaying them
|
5
|
+
# http://en.wikipedia.org/wiki/Significant_figures
|
6
|
+
|
7
|
+
attr_reader :data_type, :name, :values, :chart_roles
|
8
|
+
delegate :available_chart_types, :to => :data_type, :prefix => false
|
9
|
+
delegate :available_chart_roles, :to => :data_type, :prefix => false
|
10
|
+
|
11
|
+
# options: :chart_roles, :data_type (all optional)
|
12
|
+
def initialize(_name, _values, options={})
|
13
|
+
options = { chart_roles: [], data_type: nil }.merge(options)
|
14
|
+
@name = _name
|
15
|
+
@values = _values
|
16
|
+
@data_type = init_data_type(options[:data_type])
|
17
|
+
@chart_roles = init_chart_roles(options[:chart_roles]) # after data_type!
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns descriptive_statistics as a flat Array
|
21
|
+
def descriptive_statistics
|
22
|
+
@data_type.descriptive_statistics(values)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns descriptive_statistics as a renderable table structure
|
26
|
+
def descriptive_statistics_table
|
27
|
+
@data_type.descriptive_statistics_table(values)
|
28
|
+
end
|
29
|
+
|
30
|
+
def values_summary
|
31
|
+
if values.length < 3 || values.inspect.length < 80
|
32
|
+
values.inspect
|
33
|
+
else
|
34
|
+
"[#{ values.first } ... #{ values.last }]"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect(indent=1, recursive=1000)
|
39
|
+
r = %(#<#{ self.class.to_s }\n)
|
40
|
+
r << [
|
41
|
+
"@name=#{ name.inspect }",
|
42
|
+
"@data_type=#{ data_type.inspect }",
|
43
|
+
"@chart_roles=#{ chart_roles.inspect }",
|
44
|
+
"@values=<count: #{ values.count }, items: #{ values_summary }>",
|
45
|
+
].map { |e| "#{ ' ' * indent }#{ e }\n"}.join
|
46
|
+
if recursive > 0
|
47
|
+
# nothing to recurse
|
48
|
+
end
|
49
|
+
r << %(#{ ' ' * (indent-1) }>\n)
|
50
|
+
end
|
51
|
+
|
52
|
+
def axis_tick_format
|
53
|
+
data_type.axis_tick_format(values)
|
54
|
+
end
|
55
|
+
|
56
|
+
def uniq_vals
|
57
|
+
@uniq_vals = values.uniq
|
58
|
+
end
|
59
|
+
|
60
|
+
def uniq_vals_count
|
61
|
+
@uniq_vals_count = uniq_vals.length
|
62
|
+
end
|
63
|
+
|
64
|
+
def min_val
|
65
|
+
@min_val = values.compact.min
|
66
|
+
end
|
67
|
+
|
68
|
+
def max_val
|
69
|
+
@max_val = values.compact.max
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# @param[Array<Symbol>] chart_role_overrides, :x, :y, :color
|
75
|
+
# @return[Hash] keys are chart_classes, and values are arrays with roles
|
76
|
+
def init_chart_roles(chart_role_overrides)
|
77
|
+
r = if chart_role_overrides.any?
|
78
|
+
available_chart_types.inject(Hash.new([])) { |m,chart_type|
|
79
|
+
subset = chart_type[:chart_roles] & chart_role_overrides
|
80
|
+
next m if subset.empty?
|
81
|
+
m[chart_type[:chart_class]] += subset
|
82
|
+
m[chart_type[:chart_class]].uniq!
|
83
|
+
m
|
84
|
+
}
|
85
|
+
else
|
86
|
+
available_chart_types.inject(Hash.new([])) { |m,chart_type|
|
87
|
+
m[chart_type[:chart_class]] += chart_type[:chart_roles]
|
88
|
+
m[chart_type[:chart_class]].uniq!
|
89
|
+
m
|
90
|
+
}
|
91
|
+
end
|
92
|
+
r.freeze
|
93
|
+
end
|
94
|
+
|
95
|
+
def init_data_type(data_type_override)
|
96
|
+
if data_type_override.nil?
|
97
|
+
case values.first
|
98
|
+
when Integer, Bignum, Fixnum
|
99
|
+
DataType::Quantitative::Integer.new
|
100
|
+
when Float
|
101
|
+
DataType::Quantitative::Decimal.new
|
102
|
+
when String
|
103
|
+
DataType::Categorical.new
|
104
|
+
when DateTime, ActiveSupport::TimeWithZone
|
105
|
+
DataType::Quantitative::Temporal.new
|
106
|
+
else
|
107
|
+
raise(ArgumentError.new("Can't infer data type for value: #{ values.first.class.inspect }"))
|
108
|
+
end
|
109
|
+
else
|
110
|
+
data_type_override
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Container for data series
|
2
|
+
class RailsDataExplorer
|
3
|
+
class DataSet
|
4
|
+
|
5
|
+
attr_reader :data_series
|
6
|
+
|
7
|
+
# @param[Array<Numeric, String, Symbol, Nil, Hash, DataSeries>] values_or_data_series
|
8
|
+
# Array can contain the following:
|
9
|
+
# * Numeric, String, Symbol, Nil - for a single data series
|
10
|
+
# * Hash - for multiple data series with the following keys:
|
11
|
+
# * :name - name for the series as String
|
12
|
+
# * :values - scalar values as array
|
13
|
+
# * :chart_roles [Array<Symbol>, optional] - what to use this series for. possible values: :x, :y, :color
|
14
|
+
# * :data_type (optional) - :quantitative, :categorical, :temporal
|
15
|
+
# * DataSeries
|
16
|
+
# @param[String] exploration_title used as fall back for data series name
|
17
|
+
def initialize(values_or_data_series, exploration_title)
|
18
|
+
@data_series = initialize_data_series(values_or_data_series, exploration_title)
|
19
|
+
validate_data_series
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize_data_series(values_or_data_series, exploration_title)
|
23
|
+
case values_or_data_series.first
|
24
|
+
when ActiveSupport::TimeWithZone, DateTime, Numeric, NilClass, String, Symbol
|
25
|
+
# Array of scalar values, convert to single data series
|
26
|
+
[DataSeries.new(exploration_title, values_or_data_series)]
|
27
|
+
when Hash
|
28
|
+
# Array of Hashes, convert each key/val pair to a data series
|
29
|
+
values_or_data_series.map { |data_series_attrs|
|
30
|
+
DataSeries.new(
|
31
|
+
data_series_attrs.delete(:name),
|
32
|
+
data_series_attrs.delete(:values),
|
33
|
+
data_series_attrs # pass remaining attrs as options
|
34
|
+
)
|
35
|
+
}
|
36
|
+
when DataSeries
|
37
|
+
# return as is
|
38
|
+
values_or_data_series
|
39
|
+
else
|
40
|
+
raise(
|
41
|
+
ArgumentError.new(
|
42
|
+
"Invalid datum. Only Hash, Numeric, String, Symbol, and Nil are allowed. " + \
|
43
|
+
"Found #{ values_or_data_series.first.class.to_s }."
|
44
|
+
)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_data_series
|
50
|
+
# all series have same size
|
51
|
+
unless 1 == @data_series.map { |e| e.values.length }.uniq.length
|
52
|
+
raise(ArgumentError.new("All data series must have same length."))
|
53
|
+
end
|
54
|
+
# presence of at least one data_series
|
55
|
+
if 0 == dimensions_count
|
56
|
+
raise(ArgumentError.new("Please provide at least 1 data series."))
|
57
|
+
end
|
58
|
+
# TODO: all elements in a series are of same type
|
59
|
+
end
|
60
|
+
|
61
|
+
def dimensions_count
|
62
|
+
@data_series.length
|
63
|
+
end
|
64
|
+
|
65
|
+
def available_chart_types
|
66
|
+
case dimensions_count
|
67
|
+
when 0
|
68
|
+
# invalid, handled in validate_data_series
|
69
|
+
when 1
|
70
|
+
# charts for a single data series, use that series' available_chart_types
|
71
|
+
@data_series.first.available_chart_types(dimensions_count: 1).map { |e| e[:chart_class] }
|
72
|
+
else
|
73
|
+
# TODO: define on each chart type which chart_roles are required.
|
74
|
+
# Then use only charts for which all roles are filled.
|
75
|
+
# charts for two data series
|
76
|
+
# find intersection of all available chart types
|
77
|
+
r = @data_series.inject(nil) { |m,ds|
|
78
|
+
constraints = { dimensions_count: dimensions_count, chart_roles: ds.chart_roles }
|
79
|
+
# initialize m with first data series
|
80
|
+
m = ds.available_chart_types(constraints).map { |e| e[:chart_class] } if m.nil?
|
81
|
+
# find intersection of all available_chart_types
|
82
|
+
m = ds.available_chart_types(constraints).map { |e| e[:chart_class] } & m
|
83
|
+
m
|
84
|
+
}
|
85
|
+
r
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def descriptive_statistics
|
90
|
+
case dimensions_count
|
91
|
+
when 0
|
92
|
+
# invalid, handled in validate_data_series
|
93
|
+
when 1
|
94
|
+
# charts for a single data series, use that series' descriptive_statistics
|
95
|
+
@data_series.first.descriptive_statistics
|
96
|
+
when 2
|
97
|
+
# charts for two data series
|
98
|
+
else
|
99
|
+
# charts for multiple data series
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def inspect(indent=1, recursive=1000)
|
104
|
+
r = %(#<#{ self.class.to_s }\n)
|
105
|
+
r << [
|
106
|
+
"@dimensions_count=#{ dimensions_count }",
|
107
|
+
].map { |e| "#{ ' ' * indent }#{ e }\n"}.join
|
108
|
+
if recursive > 0
|
109
|
+
# data_series
|
110
|
+
r << %(#{ ' ' * indent }@data_series=[\n)
|
111
|
+
data_series.each do |e|
|
112
|
+
r << "#{ ' ' * (indent + 1) }"
|
113
|
+
r << e.inspect(indent + 2, recursive - 1)
|
114
|
+
end
|
115
|
+
r << "#{ ' ' * indent }]\n"
|
116
|
+
# available_chart_types
|
117
|
+
r << %(#{ ' ' * indent }@available_chart_types=[\n)
|
118
|
+
available_chart_types.each do |e|
|
119
|
+
r << "#{ ' ' * (indent + 1) }#{ e.inspect }\n"
|
120
|
+
end
|
121
|
+
r << "#{ ' ' * indent }]\n"
|
122
|
+
end
|
123
|
+
r << %(#{ ' ' * (indent-1) }>\n)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|