rails-data-explorer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile +7 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +52 -0
  6. data/Rakefile +18 -0
  7. data/lib/rails-data-explorer.rb +44 -0
  8. data/lib/rails-data-explorer/action_view_extension.rb +12 -0
  9. data/lib/rails-data-explorer/active_record_extension.rb +14 -0
  10. data/lib/rails-data-explorer/chart.rb +52 -0
  11. data/lib/rails-data-explorer/chart/box_plot.rb +79 -0
  12. data/lib/rails-data-explorer/chart/box_plot_group.rb +109 -0
  13. data/lib/rails-data-explorer/chart/contingency_table.rb +189 -0
  14. data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +22 -0
  15. data/lib/rails-data-explorer/chart/descriptive_statistics_table_group.rb +0 -0
  16. data/lib/rails-data-explorer/chart/histogram_categorical.rb +73 -0
  17. data/lib/rails-data-explorer/chart/histogram_quantitative.rb +73 -0
  18. data/lib/rails-data-explorer/chart/histogram_temporal.rb +78 -0
  19. data/lib/rails-data-explorer/chart/multi_dimensional_charts.rb +1 -0
  20. data/lib/rails-data-explorer/chart/parallel_coordinates.rb +89 -0
  21. data/lib/rails-data-explorer/chart/parallel_set.rb +65 -0
  22. data/lib/rails-data-explorer/chart/pie_chart.rb +67 -0
  23. data/lib/rails-data-explorer/chart/scatterplot.rb +120 -0
  24. data/lib/rails-data-explorer/chart/scatterplot_matrix.rb +1 -0
  25. data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +120 -0
  26. data/lib/rails-data-explorer/data_series.rb +115 -0
  27. data/lib/rails-data-explorer/data_set.rb +127 -0
  28. data/lib/rails-data-explorer/data_type.rb +34 -0
  29. data/lib/rails-data-explorer/data_type/categorical.rb +117 -0
  30. data/lib/rails-data-explorer/data_type/geo.rb +1 -0
  31. data/lib/rails-data-explorer/data_type/quantitative.rb +109 -0
  32. data/lib/rails-data-explorer/data_type/quantitative/decimal.rb +13 -0
  33. data/lib/rails-data-explorer/data_type/quantitative/integer.rb +13 -0
  34. data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +62 -0
  35. data/lib/rails-data-explorer/engine.rb +24 -0
  36. data/lib/rails-data-explorer/exploration.rb +89 -0
  37. data/lib/rails-data-explorer/statistics/pearsons_chi_squared_independence_test.rb +75 -0
  38. data/lib/rails-data-explorer/statistics/rng_category.rb +37 -0
  39. data/lib/rails-data-explorer/statistics/rng_gaussian.rb +24 -0
  40. data/lib/rails-data-explorer/statistics/rng_power_law.rb +21 -0
  41. data/lib/rails-data-explorer/utils/color_scale.rb +33 -0
  42. data/lib/rails-data-explorer/utils/data_binner.rb +8 -0
  43. data/lib/rails-data-explorer/utils/data_encoder.rb +2 -0
  44. data/lib/rails-data-explorer/utils/data_quantizer.rb +2 -0
  45. data/lib/rails-data-explorer/utils/value_formatter.rb +41 -0
  46. data/rails-data-explorer.gemspec +30 -0
  47. data/vendor/assets/javascripts/d3.boxplot.js +302 -0
  48. data/vendor/assets/javascripts/d3.parcoords.js +585 -0
  49. data/vendor/assets/javascripts/d3.parsets.js +663 -0
  50. data/vendor/assets/javascripts/d3.v3.js +9294 -0
  51. data/vendor/assets/javascripts/nv.d3.js +14369 -0
  52. data/vendor/assets/javascripts/rails-data-explorer.js +19 -0
  53. data/vendor/assets/stylesheets/bootstrap-theme.css +346 -0
  54. data/vendor/assets/stylesheets/bootstrap.css +1727 -0
  55. data/vendor/assets/stylesheets/d3.boxplot.css +20 -0
  56. data/vendor/assets/stylesheets/d3.parcoords.css +34 -0
  57. data/vendor/assets/stylesheets/d3.parsets.css +34 -0
  58. data/vendor/assets/stylesheets/nv.d3.css +769 -0
  59. data/vendor/assets/stylesheets/rails-data-explorer.css +21 -0
  60. data/vendor/assets/stylesheets/rde-default-style.css +42 -0
  61. metadata +250 -0
@@ -0,0 +1,75 @@
1
+ =begin
2
+
3
+ From http://en.wikipedia.org/wiki/Pearson's_chi-squared_test
4
+
5
+ Pearson's chi-squared test is used to assess whether paired observations on two
6
+ variables, expressed in a contingency table, are independent of each other.
7
+
8
+ An "observation" consists of the values of two outcomes and the null hypothesis
9
+ is that the occurrence of these outcomes is statistically independent. Each
10
+ observation is allocated to one cell of a two-dimensional array of cells (called
11
+ a contingency table) according to the values of the two outcomes.
12
+
13
+ Assumptions
14
+ -----------
15
+
16
+ The chi-squared test, when used with the standard approximation that a chi-
17
+ squared distribution is applicable, has the following assumptions:
18
+
19
+ * Simple random sample – The sample data is a random sampling from a fixed
20
+ distribution or population where every collection of members of the population
21
+ of the given sample size has an equal probability of selection. Variants of
22
+ the test have been developed for complex samples, such as where the data is
23
+ weighted. Other forms can be used such as purposive sampling.
24
+ * Sample size (whole table) – A sample with a sufficiently large size is assumed.
25
+ If a chi squared test is conducted on a sample with a smaller size, then the
26
+ chi squared test will yield an inaccurate inference. The researcher, by using
27
+ chi squared test on small samples, might end up committing a Type II error.
28
+ * Expected cell count – Adequate expected cell counts. Some require 5 or more,
29
+ and others require 10 or more. A common rule is 5 or more in all cells of a
30
+ 2-by-2 table, and 5 or more in 80% of cells in larger tables, but no cells
31
+ with zero expected count. When this assumption is not met, Yates's Correction
32
+ is applied.
33
+ * Independence – The observations are always assumed to be independent of each
34
+ other. This means chi-squared cannot be used to test correlated data
35
+ (like matched pairs or panel data). In those cases you might want to turn to
36
+ McNemar's test.
37
+
38
+ Problems
39
+ --------
40
+
41
+ The approximation to the chi-squared distribution breaks down if expected
42
+ frequencies are too low. It will normally be acceptable so long as no more than
43
+ 20% of the events have expected frequencies below 5. Where there is only 1
44
+ degree of freedom, the approximation is not reliable if expected frequencies are
45
+ below 10. In this case, a better approximation can be obtained by reducing the
46
+ absolute value of each difference between observed and expected frequencies by
47
+ 0.5 before squaring; this is called Yates's correction for continuity.
48
+
49
+ In cases where the expected value, E, is found to be small (indicating a small
50
+ underlying population probability, and/or a small number of observations), the
51
+ normal approximation of the multinomial distribution can fail, and in such cases
52
+ it is found to be more appropriate to use the G-test, a likelihood ratio-based
53
+ test statistic. Where the total sample size is small, it is necessary to use an
54
+ appropriate exact test, typically either the binomial test or (for contingency
55
+ tables) Fisher's exact test. This test uses the conditional distribution of the
56
+ test statistic given the marginal totals; however, it does not assume that the
57
+ data were generated from an experiment in which the marginal totals are fixed
58
+ and is valid whether or not that is the case.
59
+
60
+ =end
61
+
62
+ class RailsDataExplorer
63
+ module Statistics
64
+ class PearsonsChiSquaredIndependenceTest
65
+
66
+ #
67
+ def initialize(data_matrix, min_probability = 0.05)
68
+ end
69
+
70
+ def compute
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,37 @@
1
+ class RailsDataExplorer
2
+ module Statistics
3
+ class RngCategory
4
+
5
+ def initialize(categories, category_probabilities = nil, rng = lambda { Kernel.rand })
6
+ @categories, @category_probabilities, @rng = categories, category_probabilities, rng
7
+ @category_probabilities ||= @categories.map { |e| @rng.call }
8
+ @category_probabilities = normalize_category_probabilities
9
+ @category_order = compute_category_order
10
+ end
11
+
12
+ def rand
13
+ r_v = @rng.call
14
+ rnd = @category_order.detect { |e|
15
+ e[:threshold] >= r_v
16
+ }
17
+ rnd[:category]
18
+ end
19
+
20
+ def normalize_category_probabilities
21
+ total = @category_probabilities.inject(0) { |m,e| m += e }
22
+ @category_probabilities.map { |e| e / total.to_f }
23
+ end
24
+
25
+ def compute_category_order
26
+ category_order = []
27
+ running_sum = 0
28
+ @categories.each_with_index { |e, idx|
29
+ running_sum += @category_probabilities[idx]
30
+ category_order << { :category => e, :threshold => running_sum }
31
+ }
32
+ category_order
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ # From http://stackoverflow.com/a/9266488
2
+ class RailsDataExplorer
3
+ module Statistics
4
+ class RngGaussian
5
+ def initialize(mean = 0.0, sd = 1.0, rng = lambda { Kernel.rand })
6
+ @mean, @sd, @rng = mean, sd, rng
7
+ @compute_next_pair = false
8
+ end
9
+
10
+ def rand
11
+ if (@compute_next_pair = !@compute_next_pair)
12
+ # Compute a pair of random values with normal distribution.
13
+ # See http://en.wikipedia.org/wiki/Box-Muller_transform
14
+ theta = 2 * Math::PI * @rng.call
15
+ scale = @sd * Math.sqrt(-2 * Math.log(1 - @rng.call))
16
+ @g1 = @mean + scale * Math.sin(theta)
17
+ @g0 = @mean + scale * Math.cos(theta)
18
+ else
19
+ @g1
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ class RailsDataExplorer
2
+ module Statistics
3
+ class RngPowerLaw
4
+
5
+ def initialize(min = 1, max = 1000, pow = 2, rng = lambda { Kernel.rand })
6
+ @min, @max, @pow, @rng = min, max, pow, rng
7
+ @max += 1
8
+ end
9
+
10
+ def rand
11
+ y = (
12
+ (
13
+ (@max ** (@pow + 1) - @min ** (@pow + 1)) * @rng.call + @min ** (@pow + 1)
14
+ ) ** (1.0 / (@pow + 1))
15
+ ).to_i
16
+ (@max - 1 - y) + @min
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ class RailsDataExplorer
2
+ module Utils
3
+ class ColorScale
4
+
5
+ def initialize
6
+ @points = {
7
+ -1 => Color::RGB::Red,
8
+ -0.1 => Color::RGB::Black,
9
+ 0.1 => Color::RGB::Black,
10
+ 1 => Color::RGB::Green,
11
+ }
12
+ @gradient = Interpolate::Points.new(@points)
13
+ @gradient.blend_with {|color, other, balance|
14
+ other.mix_with(color, balance * 100.0)
15
+ }
16
+ end
17
+
18
+ def compute(input)
19
+ @gradient.at(input).html
20
+ end
21
+
22
+ def demo
23
+ "<ul>".html_safe +
24
+ (-1).step(1, 0.1).map { |e|
25
+ color = compute(e)
26
+ %(<li style="color: #{ color }">#{ e } (#{ color })</li>)
27
+ }.join.html_safe +
28
+ "</ul>".html_safe
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ # Convert quantitative data to categorical data.
2
+ # http://saedsayad.com/binning.htm
3
+ # E.g., ages in years to 5 groups:
4
+ # * < 10
5
+ # * 11 - 20
6
+ # * 21 - 30
7
+ # * 31 - 40
8
+ # * > 40
@@ -0,0 +1,2 @@
1
+ # Convert categorical data to quantitative data.
2
+ # http://saedsayad.com/encoding.htm
@@ -0,0 +1,2 @@
1
+ # Map a large set of quantitative/temporal/geo input values to a (countable)
2
+ # smaller set – such as rounding values to some unit of precision.
@@ -0,0 +1,41 @@
1
+ # Add ValueFormat to DataSeries and to individual data points
2
+ # Good resource on significant figures:
3
+ # * http://www.edu.pe.ca/gray/class_pages/krcutcliffe/physics521/sigfigs/sigfigRULES.htm
4
+ # * http://en.wikipedia.org/wiki/Significant_figures
5
+ class RailsDataExplorer
6
+ module Utils
7
+ class ValueFormatter
8
+
9
+ attr_accessor :d3_format, :ruby_formatter, :significant_figures
10
+
11
+ # @param[Object] context
12
+ def initialize(context)
13
+ case context
14
+ when DataSeries
15
+ initialize_from_data_series(context)
16
+ when Hash
17
+ initialize_from_options(context)
18
+ when Numeric
19
+ initialize_from_single_value(context)
20
+ else
21
+ raise "Handle this context: #{ context.inspect }"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def initialize_from_data_series(data_series)
28
+ end
29
+
30
+ def initialize_from_options(options)
31
+ @d3_format = options[:d3_format]
32
+ @significant_figures = options[:significant_figures]
33
+ @ruby_formatter = options[:ruby_formatter]
34
+ end
35
+
36
+ def initialize_from_single_value(options)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'rails-data-explorer'
6
+ gem.version = '0.0.1'
7
+ gem.platform = Gem::Platform::RUBY
8
+
9
+ gem.authors = ['Jo Hund']
10
+ gem.email = 'jhund@clearcove.ca'
11
+ gem.homepage = 'http://rails-data-explorer.clearcove.ca'
12
+ gem.licenses = ['MIT']
13
+ gem.summary = 'A Rails engine plugin for exploring data in your app with charts and statistics.'
14
+ gem.description = %(rails-data-explorer is a Rails Engine plugin that makes it easy to explore the data in your app using charts and statistics.)
15
+
16
+ gem.files = `git ls-files`.split("\n")
17
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+
19
+ # really it's only ActiveSupport and ActionView
20
+ gem.add_dependency 'actionview', '>= 3.0.0'
21
+ gem.add_dependency 'color'
22
+ gem.add_dependency 'descriptive-statistics'
23
+ gem.add_dependency 'distribution'
24
+ gem.add_dependency 'interpolate'
25
+
26
+ gem.add_development_dependency 'bundler', ['>= 1.0.0']
27
+ gem.add_development_dependency 'rake', ['>= 0']
28
+ gem.add_development_dependency 'minitest'
29
+ gem.add_development_dependency 'minitest-spec-expect'
30
+ end
@@ -0,0 +1,302 @@
1
+ // From http://bl.ocks.org/mbostock/4061502
2
+ (function() {
3
+
4
+ // Inspired by http://informationandvisualization.de/blog/box-plot
5
+ d3.box = function() {
6
+ var width = 1,
7
+ height = 1,
8
+ duration = 0,
9
+ domain = null,
10
+ value = Number,
11
+ whiskers = boxWhiskers,
12
+ quartiles = boxQuartiles,
13
+ tickFormat = null;
14
+
15
+ // For each small multiple…
16
+ function box(g) {
17
+ g.each(function(d, i) {
18
+ d = d.map(value).sort(d3.ascending);
19
+ var g = d3.select(this),
20
+ n = d.length,
21
+ min = d[0],
22
+ max = d[n - 1];
23
+
24
+ // Compute quartiles. Must return exactly 3 elements.
25
+ var quartileData = d.quartiles = quartiles(d);
26
+
27
+ // Compute whiskers. Must return exactly 2 elements, or null.
28
+ var whiskerIndices = whiskers && whiskers.call(this, d, i),
29
+ whiskerData = whiskerIndices && whiskerIndices.map(function(i) { return d[i]; });
30
+
31
+ // Compute outliers. If no whiskers are specified, all data are "outliers".
32
+ // We compute the outliers as indices, so that we can join across transitions!
33
+ var outlierIndices = whiskerIndices
34
+ ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))
35
+ : d3.range(n);
36
+
37
+ // Compute the new x-scale.
38
+ var x1 = d3.scale.linear()
39
+ .domain(domain && domain.call(this, d, i) || [min, max])
40
+ .range([height, 0]);
41
+
42
+ // Retrieve the old x-scale, if this is an update.
43
+ var x0 = this.__chart__ || d3.scale.linear()
44
+ .domain([0, Infinity])
45
+ .range(x1.range());
46
+
47
+ // Stash the new scale.
48
+ this.__chart__ = x1;
49
+
50
+ // Note: the box, median, and box tick elements are fixed in number,
51
+ // so we only have to handle enter and update. In contrast, the outliers
52
+ // and other elements are variable, so we need to exit them! Variable
53
+ // elements also fade in and out.
54
+
55
+ // Update center line: the vertical line spanning the whiskers.
56
+ var center = g.selectAll("line.center")
57
+ .data(whiskerData ? [whiskerData] : []);
58
+
59
+ center.enter().insert("line", "rect")
60
+ .attr("class", "center")
61
+ .attr("x1", width / 2)
62
+ .attr("y1", function(d) { return x0(d[0]); })
63
+ .attr("x2", width / 2)
64
+ .attr("y2", function(d) { return x0(d[1]); })
65
+ .style("opacity", 1e-6)
66
+ .transition()
67
+ .duration(duration)
68
+ .style("opacity", 1)
69
+ .attr("y1", function(d) { return x1(d[0]); })
70
+ .attr("y2", function(d) { return x1(d[1]); });
71
+
72
+ center.transition()
73
+ .duration(duration)
74
+ .style("opacity", 1)
75
+ .attr("y1", function(d) { return x1(d[0]); })
76
+ .attr("y2", function(d) { return x1(d[1]); });
77
+
78
+ center.exit().transition()
79
+ .duration(duration)
80
+ .style("opacity", 1e-6)
81
+ .attr("y1", function(d) { return x1(d[0]); })
82
+ .attr("y2", function(d) { return x1(d[1]); })
83
+ .remove();
84
+
85
+ // Update innerquartile box.
86
+ var box = g.selectAll("rect.box")
87
+ .data([quartileData]);
88
+
89
+ box.enter().append("rect")
90
+ .attr("class", "box")
91
+ .attr("x", 0)
92
+ .attr("y", function(d) { return x0(d[2]); })
93
+ .attr("width", width)
94
+ .attr("height", function(d) { return x0(d[0]) - x0(d[2]); })
95
+ .transition()
96
+ .duration(duration)
97
+ .attr("y", function(d) { return x1(d[2]); })
98
+ .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
99
+
100
+ box.transition()
101
+ .duration(duration)
102
+ .attr("y", function(d) { return x1(d[2]); })
103
+ .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
104
+
105
+ // Update median line.
106
+ var medianLine = g.selectAll("line.median")
107
+ .data([quartileData[1]]);
108
+
109
+ medianLine.enter().append("line")
110
+ .attr("class", "median")
111
+ .attr("x1", 0)
112
+ .attr("y1", x0)
113
+ .attr("x2", width)
114
+ .attr("y2", x0)
115
+ .transition()
116
+ .duration(duration)
117
+ .attr("y1", x1)
118
+ .attr("y2", x1);
119
+
120
+ medianLine.transition()
121
+ .duration(duration)
122
+ .attr("y1", x1)
123
+ .attr("y2", x1);
124
+
125
+ // Update whiskers.
126
+ var whisker = g.selectAll("line.whisker")
127
+ .data(whiskerData || []);
128
+
129
+ whisker.enter().insert("line", "circle, text")
130
+ .attr("class", "whisker")
131
+ .attr("x1", 0)
132
+ .attr("y1", x0)
133
+ .attr("x2", width)
134
+ .attr("y2", x0)
135
+ .style("opacity", 1e-6)
136
+ .transition()
137
+ .duration(duration)
138
+ .attr("y1", x1)
139
+ .attr("y2", x1)
140
+ .style("opacity", 1);
141
+
142
+ whisker.transition()
143
+ .duration(duration)
144
+ .attr("y1", x1)
145
+ .attr("y2", x1)
146
+ .style("opacity", 1);
147
+
148
+ whisker.exit().transition()
149
+ .duration(duration)
150
+ .attr("y1", x1)
151
+ .attr("y2", x1)
152
+ .style("opacity", 1e-6)
153
+ .remove();
154
+
155
+ // Update outliers.
156
+ var outlier = g.selectAll("circle.outlier")
157
+ .data(outlierIndices, Number);
158
+
159
+ outlier.enter().insert("circle", "text")
160
+ .attr("class", "outlier")
161
+ .attr("r", 5)
162
+ .attr("cx", width / 2)
163
+ .attr("cy", function(i) { return x0(d[i]); })
164
+ .style("opacity", 1e-6)
165
+ .transition()
166
+ .duration(duration)
167
+ .attr("cy", function(i) { return x1(d[i]); })
168
+ .style("opacity", 1);
169
+
170
+ outlier.transition()
171
+ .duration(duration)
172
+ .attr("cy", function(i) { return x1(d[i]); })
173
+ .style("opacity", 1);
174
+
175
+ outlier.exit().transition()
176
+ .duration(duration)
177
+ .attr("cy", function(i) { return x1(d[i]); })
178
+ .style("opacity", 1e-6)
179
+ .remove();
180
+
181
+ // Compute the tick format.
182
+ var format = tickFormat || x1.tickFormat(8);
183
+
184
+ // Update box ticks.
185
+ var boxTick = g.selectAll("text.box")
186
+ .data(quartileData);
187
+
188
+ boxTick.enter().append("text")
189
+ .attr("class", "box")
190
+ .attr("dy", ".3em")
191
+ .attr("dx", function(d, i) { return i & 1 ? 6 : -6 })
192
+ .attr("x", function(d, i) { return i & 1 ? width : 0 })
193
+ .attr("y", x0)
194
+ .attr("text-anchor", function(d, i) { return i & 1 ? "start" : "end"; })
195
+ .text(format)
196
+ .transition()
197
+ .duration(duration)
198
+ .attr("y", x1);
199
+
200
+ boxTick.transition()
201
+ .duration(duration)
202
+ .text(format)
203
+ .attr("y", x1);
204
+
205
+ // Update whisker ticks. These are handled separately from the box
206
+ // ticks because they may or may not exist, and we want don't want
207
+ // to join box ticks pre-transition with whisker ticks post-.
208
+ var whiskerTick = g.selectAll("text.whisker")
209
+ .data(whiskerData || []);
210
+
211
+ whiskerTick.enter().append("text")
212
+ .attr("class", "whisker")
213
+ .attr("dy", ".3em")
214
+ .attr("dx", 6)
215
+ .attr("x", width)
216
+ .attr("y", x0)
217
+ .text(format)
218
+ .style("opacity", 1e-6)
219
+ .transition()
220
+ .duration(duration)
221
+ .attr("y", x1)
222
+ .style("opacity", 1);
223
+
224
+ whiskerTick.transition()
225
+ .duration(duration)
226
+ .text(format)
227
+ .attr("y", x1)
228
+ .style("opacity", 1);
229
+
230
+ whiskerTick.exit().transition()
231
+ .duration(duration)
232
+ .attr("y", x1)
233
+ .style("opacity", 1e-6)
234
+ .remove();
235
+ });
236
+ d3.timer.flush();
237
+ }
238
+
239
+ box.width = function(x) {
240
+ if (!arguments.length) return width;
241
+ width = x;
242
+ return box;
243
+ };
244
+
245
+ box.height = function(x) {
246
+ if (!arguments.length) return height;
247
+ height = x;
248
+ return box;
249
+ };
250
+
251
+ box.tickFormat = function(x) {
252
+ if (!arguments.length) return tickFormat;
253
+ tickFormat = x;
254
+ return box;
255
+ };
256
+
257
+ box.duration = function(x) {
258
+ if (!arguments.length) return duration;
259
+ duration = x;
260
+ return box;
261
+ };
262
+
263
+ box.domain = function(x) {
264
+ if (!arguments.length) return domain;
265
+ domain = x == null ? x : d3.functor(x);
266
+ return box;
267
+ };
268
+
269
+ box.value = function(x) {
270
+ if (!arguments.length) return value;
271
+ value = x;
272
+ return box;
273
+ };
274
+
275
+ box.whiskers = function(x) {
276
+ if (!arguments.length) return whiskers;
277
+ whiskers = x;
278
+ return box;
279
+ };
280
+
281
+ box.quartiles = function(x) {
282
+ if (!arguments.length) return quartiles;
283
+ quartiles = x;
284
+ return box;
285
+ };
286
+
287
+ return box;
288
+ };
289
+
290
+ function boxWhiskers(d) {
291
+ return [0, d.length - 1];
292
+ }
293
+
294
+ function boxQuartiles(d) {
295
+ return [
296
+ d3.quantile(d, .25),
297
+ d3.quantile(d, .5),
298
+ d3.quantile(d, .75)
299
+ ];
300
+ }
301
+
302
+ })();