rails-data-explorer 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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) {