rails-data-explorer 0.0.1 → 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.
Files changed (52) hide show
  1. data/CHANGELOG.md +5 -1
  2. data/README.md +11 -0
  3. data/Rakefile +62 -0
  4. data/doc/how_to/release.md +23 -0
  5. data/doc/how_to/trouble_when_packaging_assets.md +8 -0
  6. data/lib/rails-data-explorer-no-rails.rb +42 -0
  7. data/lib/rails-data-explorer.rb +5 -9
  8. data/lib/rails-data-explorer/chart/box_plot.rb +5 -1
  9. data/lib/rails-data-explorer/chart/box_plot_group.rb +22 -5
  10. data/lib/rails-data-explorer/chart/contingency_table.rb +45 -10
  11. data/lib/rails-data-explorer/chart/histogram_categorical.rb +104 -3
  12. data/lib/rails-data-explorer/chart/histogram_quantitative.rb +99 -2
  13. data/lib/rails-data-explorer/chart/histogram_temporal.rb +10 -55
  14. data/lib/rails-data-explorer/chart/parallel_coordinates.rb +4 -0
  15. data/lib/rails-data-explorer/chart/parallel_set.rb +4 -0
  16. data/lib/rails-data-explorer/chart/pie_chart.rb +89 -8
  17. data/lib/rails-data-explorer/chart/scatterplot.rb +110 -8
  18. data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +133 -14
  19. data/lib/rails-data-explorer/data_series.rb +37 -2
  20. data/lib/rails-data-explorer/data_type/categorical.rb +72 -2
  21. data/lib/rails-data-explorer/data_type/quantitative.rb +41 -12
  22. data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +3 -2
  23. data/lib/rails-data-explorer/exploration.rb +5 -1
  24. data/lib/rails-data-explorer/utils/data_binner.rb +31 -0
  25. data/lib/rails-data-explorer/utils/data_quantizer.rb +66 -0
  26. data/lib/rails_data_explorer.rb +133 -0
  27. data/rails-data-explorer.gemspec +4 -4
  28. data/spec/helper.rb +7 -0
  29. data/spec/helper_no_rails.rb +10 -0
  30. data/spec/rails-data-explorer/data_series_spec.rb +45 -0
  31. data/spec/rails-data-explorer/data_type/categorical_spec.rb +34 -0
  32. data/spec/rails-data-explorer/exploration_spec.rb +55 -0
  33. data/spec/rails-data-explorer/utils/data_binner_spec.rb +29 -0
  34. data/spec/rails-data-explorer/utils/data_quantizer_spec.rb +71 -0
  35. data/vendor/assets/javascripts/packaged/rails-data-explorer.min.js +1 -0
  36. data/vendor/assets/javascripts/rails-data-explorer.js +6 -5
  37. data/vendor/assets/javascripts/{d3.boxplot.js → sources/d3.boxplot.js} +10 -3
  38. data/vendor/assets/javascripts/{d3.parcoords.js → sources/d3.parcoords.js} +1 -1
  39. data/vendor/assets/javascripts/{d3.parsets.js → sources/d3.parsets.js} +3 -3
  40. data/vendor/assets/javascripts/{d3.v3.js → sources/d3.v3.js} +0 -0
  41. data/vendor/assets/javascripts/{nv.d3.js → sources/nv.d3.js} +0 -0
  42. data/vendor/assets/javascripts/sources/vega.js +7040 -0
  43. data/vendor/assets/stylesheets/packaged/rails-data-explorer.min.css +9 -0
  44. data/vendor/assets/stylesheets/rails-data-explorer.css +7 -7
  45. data/vendor/assets/stylesheets/{bootstrap-theme.css → sources/bootstrap-theme.css} +0 -0
  46. data/vendor/assets/stylesheets/{bootstrap.css → sources/bootstrap.css} +0 -0
  47. data/vendor/assets/stylesheets/{d3.boxplot.css → sources/d3.boxplot.css} +0 -0
  48. data/vendor/assets/stylesheets/{d3.parcoords.css → sources/d3.parcoords.css} +0 -0
  49. data/vendor/assets/stylesheets/{d3.parsets.css → sources/d3.parsets.css} +0 -0
  50. data/vendor/assets/stylesheets/{nv.d3.css → sources/nv.d3.css} +0 -0
  51. data/vendor/assets/stylesheets/{rde-default-style.css → sources/rde-default-style.css} +0 -0
  52. metadata +65 -28
@@ -9,12 +9,22 @@ class RailsDataExplorer
9
9
 
10
10
  def compute_chart_attrs
11
11
  x_ds = @data_set.data_series.first
12
+ return false if x_ds.nil?
13
+
12
14
  # compute histogram
13
- h = x_ds.values.inject(Hash.new(0)) { |m,e| m[e] += 1; m }
15
+ quantizer = Utils::DataQuantizer.new(x_ds, :max_number_of_bins => 100)
16
+ quantized_values = quantizer.values
17
+ number_of_bars = quantizer.number_of_bins
18
+ width = 800
19
+ h = quantized_values.inject(Hash.new(0)) { |m,e| m[e] += 1; m }
14
20
  {
15
21
  values: h.map { |k,v| { x: k, y: v } },
22
+ width: width,
16
23
  x_axis_label: x_ds.name,
17
24
  x_axis_tick_format: x_ds.axis_tick_format,
25
+ x_scale_type: 'linear',
26
+ x_scale_nice: true,
27
+ bar_width: (width / number_of_bars.to_f) - 3,
18
28
  y_axis_label: 'Frequency',
19
29
  y_axis_tick_format: "d3.format('r')",
20
30
  }
@@ -23,8 +33,89 @@ class RailsDataExplorer
23
33
  def render
24
34
  return '' unless render?
25
35
  ca = compute_chart_attrs
36
+ return '' unless ca
37
+ render_vega(ca)
38
+ end
39
+
40
+ def render_vega(ca)
26
41
  %(
27
- <div class="rde-chart rde-histogram">
42
+ <div class="rde-chart rde-histogram-quantitative">
43
+ <h3 class="rde-chart-title">Histogram</h3>
44
+ <div id="#{ dom_id }"></div>
45
+ <script type="text/javascript">
46
+ (function() {
47
+ var spec = {
48
+ "width": #{ ca[:width] },
49
+ "height": 200,
50
+ "padding": {"top": 10, "left": 70, "bottom": 50, "right": 10},
51
+ "data": [
52
+ {
53
+ "name": "table",
54
+ "values": #{ ca[:values].to_json }
55
+ }
56
+ ],
57
+ "scales": [
58
+ {
59
+ "name": "x",
60
+ "type": "#{ ca[:x_scale_type] }",
61
+ "range": "width",
62
+ "zero": false,
63
+ "nice": #{ ca[:x_scale_nice] },
64
+ "domain": {"data": "table", "field": "data.x"}
65
+ },
66
+ {
67
+ "name": "y",
68
+ "range": "height",
69
+ "domain": {"data": "table", "field": "data.y"}
70
+ }
71
+ ],
72
+ "axes": [
73
+ {
74
+ "type": "x",
75
+ "scale": "x",
76
+ "title": "#{ ca[:x_axis_label] }",
77
+ "format": #{ ca[:x_axis_tick_format] },
78
+ },
79
+ {
80
+ "type": "y",
81
+ "scale": "y",
82
+ "title": "#{ ca[:y_axis_label] }",
83
+ "titleOffset": 60,
84
+ "format": #{ ca[:y_axis_tick_format] },
85
+ }
86
+ ],
87
+ "marks": [
88
+ {
89
+ "type": "rect",
90
+ "from": {"data": "table"},
91
+ "properties": {
92
+ "enter": {
93
+ "x": {"scale": "x", "field": "data.x"},
94
+ "width": { "value": #{ ca[:bar_width] } },
95
+ "y": {"scale": "y", "field": "data.y"},
96
+ "y2": {"scale": "y", "value": 0},
97
+ },
98
+ "update": {
99
+ "fill": {"value": "#1F77B4"}
100
+ },
101
+ }
102
+ }
103
+ ]
104
+ };
105
+
106
+ vg.parse.spec(spec, function(chart) {
107
+ var view = chart({ el:"##{ dom_id }" }).update();
108
+ });
109
+
110
+ })();
111
+ </script>
112
+ </div>
113
+ )
114
+ end
115
+
116
+ def render_nvd3(ca)
117
+ %(
118
+ <div class="rde-chart rde-histogram-quantitative">
28
119
  <h3 class="rde-chart-title">Histogram</h3>
29
120
  <div id="#{ dom_id }", style="height: 200px;">
30
121
  <svg></svg>
@@ -52,6 +143,12 @@ class RailsDataExplorer
52
143
  .tickFormat(#{ ca[:y_axis_tick_format] })
53
144
  ;
54
145
 
146
+ chart.tooltipContent(
147
+ function(key, x, y, e, graph) {
148
+ return '<p>' + key + '</p>' + '<p>' + y + ' at ' + x + '</p>'
149
+ }
150
+ );
151
+
55
152
  d3.select('##{ dom_id } svg')
56
153
  .datum(data)
57
154
  .transition().duration(100)
@@ -1,78 +1,33 @@
1
+ # TODO: could I use histogram_quantitative instead and just tweak the tick mark format?
1
2
  class RailsDataExplorer
2
3
  class Chart
3
- class HistogramTemporal < Chart
4
-
5
- def initialize(_data_set, options = {})
6
- @data_set = _data_set
7
- @options = {}.merge(options)
8
- end
4
+ class HistogramTemporal < HistogramQuantitative
9
5
 
10
6
  def compute_chart_attrs
11
7
  x_ds = @data_set.data_series.first
8
+ return false if x_ds.nil?
9
+
12
10
  # compute histogram
13
11
  h = x_ds.values.inject(Hash.new(0)) { |m,e|
14
12
  # Round to day
15
- key = (e.beginning_of_day).to_i * 1000
13
+ key = e.nil? ? nil : (e.beginning_of_day).to_i * 1000
16
14
  m[key] += 1
17
15
  m
18
16
  }
17
+ width = 800
19
18
  {
20
19
  values: h.map { |k,v| { x: k, y: v } },
20
+ width: width,
21
21
  x_axis_label: x_ds.name,
22
22
  x_axis_tick_format: x_ds.axis_tick_format,
23
+ x_scale_type: 'time',
24
+ x_scale_nice: "'day'",
25
+ bar_width: 2,
23
26
  y_axis_label: 'Frequency',
24
27
  y_axis_tick_format: "d3.format('r')",
25
28
  }
26
29
  end
27
30
 
28
- def render
29
- return '' unless render?
30
- ca = compute_chart_attrs
31
- %(
32
- <div class="rde-chart rde-histogram">
33
- <h3 class="rde-chart-title">Histogram</h3>
34
- <div id="#{ dom_id }", style="height: 200px;">
35
- <svg></svg>
36
- </div>
37
- <script type="text/javascript">
38
- (function() {
39
- var data = [
40
- {
41
- values: #{ ca[:values].to_json },
42
- key: '#{ ca[:x_axis_label] }'
43
- }
44
- ];
45
-
46
- nv.addGraph(function() {
47
- var chart = nv.models.historicalBarChart()
48
- ;
49
-
50
- chart.xAxis
51
- .axisLabel('#{ ca[:x_axis_label] }')
52
- .tickFormat(#{ ca[:x_axis_tick_format] })
53
- ;
54
-
55
- chart.yAxis
56
- .axisLabel('#{ ca[:y_axis_label] }')
57
- .tickFormat(#{ ca[:y_axis_tick_format] })
58
- ;
59
-
60
- d3.select('##{ dom_id } svg')
61
- .datum(data)
62
- .transition().duration(100)
63
- .call(chart)
64
- ;
65
-
66
- nv.utils.windowResize(chart.update);
67
-
68
- return chart;
69
- });
70
- })();
71
- </script>
72
- </div>
73
- )
74
- end
75
-
76
31
  end
77
32
  end
78
33
  end
@@ -11,6 +11,8 @@ class RailsDataExplorer
11
11
  def render
12
12
  return '' unless render?
13
13
  ca = compute_chart_attrs
14
+ return '' unless ca
15
+
14
16
  %(
15
17
  <div class="rde-chart rde-parallel-coordinates">
16
18
  <h3 class="rde-chart-title">Parallel coordinates</h3>
@@ -50,6 +52,8 @@ class RailsDataExplorer
50
52
  dimension_data_series = @data_set.data_series.find_all { |ds|
51
53
  (ds.chart_roles[Chart::ParallelCoordinates] & [:dimension, :any]).any?
52
54
  }
55
+ return false if dimension_data_series.empty?
56
+
53
57
  dimension_names = dimension_data_series.map(&:name)
54
58
  number_of_values = dimension_data_series.first.values.length
55
59
  dimension_values = number_of_values.times.map do |idx|
@@ -13,6 +13,8 @@ class RailsDataExplorer
13
13
  dimension_data_series = @data_set.data_series.find_all { |ds|
14
14
  (ds.chart_roles[Chart::ParallelCoordinates] & [:dimension, :any]).any?
15
15
  }
16
+ return false if dimension_data_series.empty?
17
+
16
18
  number_of_values = dimension_data_series.first.values.length
17
19
  dimension_names = dimension_data_series.map(&:name)
18
20
  dimension_values = number_of_values.times.map do |idx|
@@ -34,6 +36,8 @@ class RailsDataExplorer
34
36
  def render
35
37
  return '' unless render?
36
38
  ca = compute_chart_attrs
39
+ return '' unless ca
40
+
37
41
  %(
38
42
  <div class="rde-chart rde-parallel-set">
39
43
  <h3 class="rde-chart-title">Parallel Set</h3>
@@ -9,25 +9,95 @@ class RailsDataExplorer
9
9
 
10
10
  def compute_chart_attrs
11
11
  x_ds = @data_set.data_series.first
12
+ return false if x_ds.nil?
13
+
12
14
  total_count = x_ds.values.length
13
15
  # compute histogram
14
16
  h = x_ds.values.inject(Hash.new(0)) { |m,e| m[e] += 1; m }
15
17
  {
16
18
  values: h.map { |k,v|
17
- { x: k, y: (v / total_count.to_f) }
18
- }.sort { |a,b|
19
- b[:y] <=> a[:y]
20
- },
21
- x_axis_label: x_ds.name,
22
- x_axis_tick_format: "",
23
- y_axis_label: 'Frequency',
24
- y_axis_tick_format: "d3.format('r')",
19
+ { key: k, value: (v / total_count.to_f) }
20
+ }.sort(
21
+ &x_ds.label_sorter(
22
+ :key,
23
+ lambda { |a,b| b[:value] <=> a[:value] }
24
+ )
25
+ )
25
26
  }
26
27
  end
27
28
 
28
29
  def render
29
30
  return '' unless render?
30
31
  ca = compute_chart_attrs
32
+ return '' unless ca
33
+ render_vega(ca)
34
+ end
35
+
36
+ def render_vega(ca)
37
+ %(
38
+ <div class="rde-chart rde-pie-chart">
39
+ <h3 class="rde-chart-title">Pie Chart</h3>
40
+ <div id="#{ dom_id }"></div>
41
+ <script type="text/javascript">
42
+ (function() {
43
+ var spec = {
44
+ "width": 300,
45
+ "height": 300,
46
+ "padding": {"top": 10, "left": 10, "bottom": 10, "right": 100},
47
+ "data": [
48
+ {
49
+ "name": "table",
50
+ "values": #{ ca[:values].to_json }
51
+ }
52
+ ],
53
+ "scales": [
54
+ {
55
+ "name": "color",
56
+ "domain": {"data": "table", "field": "data.key"},
57
+ "range": "category10",
58
+ "type": "ordinal"
59
+ },
60
+ ],
61
+ "axes": [],
62
+ "marks": [
63
+ {
64
+ "type": "arc",
65
+ "from": {
66
+ "data": "table",
67
+ "transform": [{"type": "pie", "value": "data.value"}]
68
+ },
69
+ "properties": {
70
+ "enter": {
71
+ "x": {"group": "width", "mult": 0.5},
72
+ "y": {"group": "height", "mult": 0.5},
73
+ "endAngle": {"field": "endAngle"},
74
+ "innerRadius": {"value": 100},
75
+ "outerRadius": {"value": 150},
76
+ "startAngle": {"field": "startAngle"},
77
+ "stroke": {"value": "white"},
78
+ "fill": {"field": "data.key", "scale": "color"}
79
+ }
80
+ }
81
+ }
82
+ ],
83
+ "legends": [
84
+ {
85
+ "fill": "color",
86
+ }
87
+ ],
88
+ };
89
+
90
+ vg.parse.spec(spec, function(chart) {
91
+ var view = chart({ el:"##{ dom_id }" }).update();
92
+ });
93
+
94
+ })();
95
+ </script>
96
+ </div>
97
+ )
98
+ end
99
+
100
+ def render_nvd3(ca)
31
101
  %(
32
102
  <div class="rde-chart rde-pie-chart">
33
103
  <h3 class="rde-chart-title">Pie Chart</h3>
@@ -45,6 +115,11 @@ class RailsDataExplorer
45
115
  chart.valueFormat(d3.format('.1%'))
46
116
  .donut(true)
47
117
  ;
118
+ chart.tooltipContent(
119
+ function(key, y, e, graph) {
120
+ return '<p>' + key + '</p>' + '<p>' + y + '</p>'
121
+ }
122
+ );
48
123
 
49
124
  d3.select('##{ dom_id } svg')
50
125
  .datum(data)
@@ -62,6 +137,12 @@ class RailsDataExplorer
62
137
  )
63
138
  end
64
139
 
140
+ # Render PieChart only if there is a fairly small number of
141
+ # distinct values.
142
+ def render?
143
+ !@data_set.data_series.first.has_many_uniq_vals?
144
+ end
145
+
65
146
  end
66
147
  end
67
148
  end
@@ -25,6 +25,7 @@ class RailsDataExplorer
25
25
  y_ds = (y_candidates - [x_ds]).first
26
26
  color_ds = (color_candidates - [x_ds, y_ds]).first
27
27
  size_ds = (size_candidates - [x_ds, y_ds, color_ds]).first
28
+ return false if x_ds.nil? || y_ds.nil?
28
29
 
29
30
  ca = case @data_set.dimensions_count
30
31
  when 0,1
@@ -37,7 +38,7 @@ class RailsDataExplorer
37
38
  r
38
39
  }
39
40
  {
40
- values: [ { key: key, values: values_hash } ],
41
+ values: values_hash,
41
42
  x_axis_label: x_ds.name,
42
43
  x_axis_tick_format: x_ds.axis_tick_format,
43
44
  y_axis_label: y_ds.name,
@@ -54,7 +55,7 @@ class RailsDataExplorer
54
55
  data_series_hash[visual_attr_ds.values[idx]] << { x: x_ds.values[idx], y: y_ds.values[idx] }
55
56
  }
56
57
  {
57
- values: data_series_hash.map { |k,v| { key: k, values: v } },
58
+ values: data_series_hash,
58
59
  x_axis_label: x_ds.name,
59
60
  x_axis_tick_format: x_ds.axis_tick_format,
60
61
  y_axis_label: y_ds.name,
@@ -67,7 +68,108 @@ class RailsDataExplorer
67
68
 
68
69
  def render
69
70
  return '' unless render?
70
- chart_attrs = compute_chart_attrs
71
+ ca = compute_chart_attrs
72
+ return '' unless ca
73
+ render_vega(ca)
74
+ end
75
+
76
+ def render_vega(ca)
77
+ %(
78
+ <div class="rde-chart rde-scatterplot">
79
+ <h3 class="rde-chart-title">Scatterplot</h3>
80
+ <div id="#{ dom_id }"></div>
81
+ <script type="text/javascript">
82
+ (function() {
83
+ var spec = {
84
+ "width": 800,
85
+ "height": 200,
86
+ "data": [
87
+ {
88
+ "name": "table",
89
+ "values": #{ ca[:values].to_json }
90
+ },
91
+ ],
92
+ "scales": [
93
+ {
94
+ "name": "x",
95
+ "nice": true,
96
+ "range": "width",
97
+ "zero": false,
98
+ "domain": {"data": "table", "field": "data.x"}
99
+ },
100
+ {
101
+ "name": "y",
102
+ "nice": true,
103
+ "range": "height",
104
+ "zero": false,
105
+ "domain": {"data": "table", "field": "data.y"}
106
+ },
107
+ // {
108
+ // "name": "c",
109
+ // "type": "ordinal",
110
+ // "domain": {"data": "iris", "field": "data.species"},
111
+ // "range": ["#800", "#080", "#008"]
112
+ // }
113
+ ],
114
+ "axes": [
115
+ {
116
+ "type": "x",
117
+ "scale": "x",
118
+ "offset": 5,
119
+ "title": "#{ ca[:x_axis_label] }",
120
+ },
121
+ {
122
+ "type": "y",
123
+ "scale": "y",
124
+ "offset": 5,
125
+ "title": "#{ ca[:y_axis_label] }",
126
+ }
127
+ ],
128
+ // "legends": [
129
+ // {
130
+ // "fill": "c",
131
+ // "title": "Species",
132
+ // "offset": 0,
133
+ // "properties": {
134
+ // "symbols": {
135
+ // "fillOpacity": {"value": 0.5},
136
+ // "stroke": {"value": "transparent"}
137
+ // }
138
+ // }
139
+ // }
140
+ // ],
141
+ "marks": [
142
+ {
143
+ "type": "symbol",
144
+ "from": {"data": "table"},
145
+ "properties": {
146
+ "enter": {
147
+ "x": {"scale": "x", "field": "data.x"},
148
+ "y": {"scale": "y", "field": "data.y"},
149
+ //"fill": {"scale": "c", "field": "data.species"},
150
+ "fill": { "value": "#1F77B4" },
151
+ "fillOpacity": {"value": 0.4},
152
+ },
153
+ "update": {
154
+ "size": {"value": 30},
155
+ "stroke": {"value": "transparent"}
156
+ },
157
+ }
158
+ }
159
+ ]
160
+ };
161
+
162
+ vg.parse.spec(spec, function(chart) {
163
+ var view = chart({ el:"##{ dom_id }" }).update();
164
+ });
165
+
166
+ })();
167
+ </script>
168
+ </div>
169
+ )
170
+ end
171
+
172
+ def render_nvd3(ca)
71
173
  %(
72
174
  <div class="rde-chart rde-scatterplot">
73
175
  <h3 class="rde-chart-title">Scatterplot</h3>
@@ -76,7 +178,7 @@ class RailsDataExplorer
76
178
  </div>
77
179
  <script type="text/javascript">
78
180
  (function() {
79
- var data = #{ chart_attrs[:values].to_json };
181
+ var data = #{ ca[:values].to_json };
80
182
 
81
183
  nv.addGraph(function() {
82
184
  var chart = nv.models.scatterChart()
@@ -87,12 +189,12 @@ class RailsDataExplorer
87
189
  .transitionDuration(300)
88
190
  ;
89
191
 
90
- chart.xAxis.tickFormat(#{ chart_attrs[:x_axis_tick_format] })
91
- .axisLabel('#{ chart_attrs[:x_axis_label] }')
192
+ chart.xAxis.tickFormat(#{ ca[:x_axis_tick_format] })
193
+ .axisLabel('#{ ca[:x_axis_label] }')
92
194
  ;
93
195
 
94
- chart.yAxis.tickFormat(#{ chart_attrs[:y_axis_tick_format] })
95
- .axisLabel('#{ chart_attrs[:y_axis_label] }')
196
+ chart.yAxis.tickFormat(#{ ca[:y_axis_tick_format] })
197
+ .axisLabel('#{ ca[:y_axis_label] }')
96
198
  ;
97
199
 
98
200
  chart.tooltipContent(function(key) {