rails-data-explorer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,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,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
|
+
})();
|