Skip to content

Commit 08a5aa3

Browse files
committed
Histogram improvements.
Rename `ticks` to `bins` to match numpy, matplotlib, and Protovis. Also allow the bins to be specified simply as a count, in which case the range is divided uniformly into the specified number of bins. The histogram layout now also takes a `range` property, which specifies the minimum and maximum value of the histogram rather than implicitly computing it from the values, which is nice if you expect values to fall in a specific range, such as [0,1].
1 parent 0e75d27 commit 08a5aa3

4 files changed

Lines changed: 141 additions & 55 deletions

File tree

d3.layout.js

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -874,59 +874,102 @@ function d3_layout_stackSum(p, d) {
874874
}
875875
d3.layout.histogram = function() {
876876
var frequency = true,
877-
value = Number,
878-
ticksFunction = d3_layout_histogramTicks;
877+
valuer = Number,
878+
ranger = d3_layout_histogramRange,
879+
binner = d3_layout_histogramBins10;
879880

880881
function histogram(data, i) {
881-
var x = data.map(value),
882-
bins = [],
882+
var bins = [],
883+
values = data.map(valuer, this),
884+
range = ranger.call(this, values, i),
885+
thresholds = binner.call(this, range, i),
883886
bin,
884-
ticks = ticksFunction.call(this, data, i),
885887
i = -1,
886-
n = x.length,
887-
m = ticks.length - 1,
888-
k = frequency ? 1 / n : 1;
888+
n = values.length,
889+
m = thresholds.length - 1,
890+
k = frequency ? 1 / n : 1,
891+
x;
889892

890893
// Initialize the bins.
891894
while (++i < m) {
892895
bin = bins[i] = [];
893-
bin.dx = ticks[i + 1] - (bin.x = ticks[i]);
896+
bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
894897
bin.y = 0;
895898
}
896899

897-
// Count the number of samples per bin.
900+
// Fill the bins.
898901
i = -1; while(++i < n) {
899-
bin = bins[d3.bisect(ticks, x[i], 0, m - 1)];
900-
bin.y += k;
901-
bin.push(data[i]);
902+
x = values[i];
903+
if ((x >= range[0]) && (x <= range[1])) {
904+
bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
905+
bin.y += k;
906+
bin.push(data[i]);
907+
}
902908
}
903909

904910
return bins;
905911
}
906912

907-
histogram.frequency = function(x) {
908-
if (!arguments.length) return frequency;
909-
frequency = !!x;
913+
// Specifies how to extract a value from the associated data. The default
914+
// value function is `Number`, which is equivalent to the identity function.
915+
histogram.value = function(x) {
916+
if (!arguments.length) return valuer;
917+
valuer = x;
910918
return histogram;
911919
};
912920

913-
histogram.ticks = function(x) {
914-
if (!arguments.length) return ticksFunction;
915-
ticksFunction = d3.functor(x);
921+
// Specifies the range of the histogram. Values outside the specified range
922+
// will be ignored. The argument `x` may be specified either as a two-element
923+
// array representing the minimum and maximum value of the range, or as a
924+
// function that returns the range given the array of values and the current
925+
// index `i`. The default range is the extent (minimum and maximum) of the
926+
// values.
927+
histogram.range = function(x) {
928+
if (!arguments.length) return ranger;
929+
ranger = d3.functor(x);
916930
return histogram;
917931
};
918932

919-
histogram.value = function(x) {
920-
if (!arguments.length) return value;
921-
value = x;
933+
// Specifies how to bin values in the histogram. The argument `x` may be
934+
// specified as a number, in which case the range of values will be split
935+
// uniformly into the given number of bins. Or, `x` may be an array of
936+
// threshold values, defining the bins; the specified array must contain the
937+
// rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x`
938+
// may be a function which is evaluated, being passed the array of values and
939+
// the current index `i`, returning an array of thresholds. The default bin
940+
// function will divide the values into ten uniform bins.
941+
histogram.bins = function(x) {
942+
if (!arguments.length) return binner;
943+
binner = typeof x === "number" ? d3_layout_histogramBins(x) : d3.functor(x);
944+
return histogram;
945+
};
946+
947+
// Specifies whether the histogram's `y` value is a count (frequency) or a
948+
// probability (density). The default value is true.
949+
histogram.frequency = function(x) {
950+
if (!arguments.length) return frequency;
951+
frequency = !!x;
922952
return histogram;
923953
};
924954

925955
return histogram;
926956
};
927957

928-
function d3_layout_histogramTicks(x) {
929-
return d3.scale.linear().domain(x).ticks(10);
958+
var d3_layout_histogramBins10 = d3_layout_histogramBins(10);
959+
960+
function d3_layout_histogramBins(n) {
961+
return function(range) {
962+
var x = -1,
963+
b = +range[0],
964+
m = (range[1] - b) / n,
965+
f = [];
966+
while (++x <= n) f[x] = m * x + b;
967+
return f;
968+
};
969+
}
970+
971+
function d3_layout_histogramRange(values) {
972+
return [d3.min(values), d3.max(values)];
930973
}
931974
d3.layout.hierarchy = function() {
932975
var sort = d3_layout_hierarchySort,

d3.layout.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/histogram/histogram.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@
3838
}
3939

4040
var w = 400,
41-
h = 400,
42-
ticks = d3.range(0, m, .25);
41+
h = 400;
4342

4443
var histogram = d3.layout.histogram()
4544
.frequency(true)
46-
.ticks(ticks);
45+
.bins(40)
46+
(data);
4747

4848
var x = d3.scale.ordinal()
49-
.domain(ticks)
49+
.domain(histogram.map(function(d) { return d.x; }))
5050
.rangeRoundBands([0, w]);
5151

5252
var y = d3.scale.linear()
53-
.domain([0, .12])
53+
.domain([0, d3.max(histogram, function(d) { return d.y; })])
5454
.range([0, h]);
5555

5656
var vis = d3.select("body").append("svg:svg")
@@ -60,7 +60,7 @@
6060
.attr("transform", "translate(.5)");
6161

6262
vis.selectAll("rect")
63-
.data(histogram(data))
63+
.data(histogram)
6464
.enter().append("svg:rect")
6565
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + (h - y(d.y)) + ")"; })
6666
.attr("width", x.rangeBand())

src/layout/histogram.js

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,99 @@
11
d3.layout.histogram = function() {
22
var frequency = true,
3-
value = Number,
4-
ticksFunction = d3_layout_histogramTicks;
3+
valuer = Number,
4+
ranger = d3_layout_histogramRange,
5+
binner = d3_layout_histogramBins10;
56

67
function histogram(data, i) {
7-
var x = data.map(value),
8-
bins = [],
8+
var bins = [],
9+
values = data.map(valuer, this),
10+
range = ranger.call(this, values, i),
11+
thresholds = binner.call(this, range, i),
912
bin,
10-
ticks = ticksFunction.call(this, data, i),
1113
i = -1,
12-
n = x.length,
13-
m = ticks.length - 1,
14-
k = frequency ? 1 / n : 1;
14+
n = values.length,
15+
m = thresholds.length - 1,
16+
k = frequency ? 1 / n : 1,
17+
x;
1518

1619
// Initialize the bins.
1720
while (++i < m) {
1821
bin = bins[i] = [];
19-
bin.dx = ticks[i + 1] - (bin.x = ticks[i]);
22+
bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
2023
bin.y = 0;
2124
}
2225

23-
// Count the number of samples per bin.
26+
// Fill the bins, ignoring values outside the range.
2427
i = -1; while(++i < n) {
25-
bin = bins[d3.bisect(ticks, x[i], 0, m - 1)];
26-
bin.y += k;
27-
bin.push(data[i]);
28+
x = values[i];
29+
if ((x >= range[0]) && (x <= range[1])) {
30+
bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
31+
bin.y += k;
32+
bin.push(data[i]);
33+
}
2834
}
2935

3036
return bins;
3137
}
3238

33-
histogram.frequency = function(x) {
34-
if (!arguments.length) return frequency;
35-
frequency = !!x;
39+
// Specifies how to extract a value from the associated data. The default
40+
// value function is `Number`, which is equivalent to the identity function.
41+
histogram.value = function(x) {
42+
if (!arguments.length) return valuer;
43+
valuer = x;
3644
return histogram;
3745
};
3846

39-
histogram.ticks = function(x) {
40-
if (!arguments.length) return ticksFunction;
41-
ticksFunction = d3.functor(x);
47+
// Specifies the range of the histogram. Values outside the specified range
48+
// will be ignored. The argument `x` may be specified either as a two-element
49+
// array representing the minimum and maximum value of the range, or as a
50+
// function that returns the range given the array of values and the current
51+
// index `i`. The default range is the extent (minimum and maximum) of the
52+
// values.
53+
histogram.range = function(x) {
54+
if (!arguments.length) return ranger;
55+
ranger = d3.functor(x);
4256
return histogram;
4357
};
4458

45-
histogram.value = function(x) {
46-
if (!arguments.length) return value;
47-
value = x;
59+
// Specifies how to bin values in the histogram. The argument `x` may be
60+
// specified as a number, in which case the range of values will be split
61+
// uniformly into the given number of bins. Or, `x` may be an array of
62+
// threshold values, defining the bins; the specified array must contain the
63+
// rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x`
64+
// may be a function which is evaluated, being passed the array of values and
65+
// the current index `i`, returning an array of thresholds. The default bin
66+
// function will divide the values into ten uniform bins.
67+
histogram.bins = function(x) {
68+
if (!arguments.length) return binner;
69+
binner = typeof x === "number" ? d3_layout_histogramBins(x) : d3.functor(x);
70+
return histogram;
71+
};
72+
73+
// Specifies whether the histogram's `y` value is a count (frequency) or a
74+
// probability (density). The default value is true.
75+
histogram.frequency = function(x) {
76+
if (!arguments.length) return frequency;
77+
frequency = !!x;
4878
return histogram;
4979
};
5080

5181
return histogram;
5282
};
5383

54-
function d3_layout_histogramTicks(x) {
55-
return d3.scale.linear().domain(x).ticks(10);
84+
var d3_layout_histogramBins10 = d3_layout_histogramBins(10);
85+
86+
function d3_layout_histogramBins(n) {
87+
return function(range) {
88+
var x = -1,
89+
b = +range[0],
90+
m = (range[1] - b) / n,
91+
f = [];
92+
while (++x <= n) f[x] = m * x + b;
93+
return f;
94+
};
95+
}
96+
97+
function d3_layout_histogramRange(values) {
98+
return [d3.min(values), d3.max(values)];
5699
}

0 commit comments

Comments
 (0)