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,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
|
+
})();
|